Search Unity

Question How do I make the head look where the camera's pointing?

Discussion in 'Scripting' started by SassyPantsy, Mar 27, 2021.

  1. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    142
    Hi all, this seems like a basic thing to do, but I just can't make it work for the past few days, I guess I don't know quaternions good enough.
    My camera system is a third-person over the shoulder type, made with a cinemachine freelook camera. It's based on the TPS Brackey's tutorial, but the movement is based on root motion animation. I tried making this work with a 'handmade' camera system, but I need the cinemachine features.

    Anyway, I just want the head to face the direction of the camera, be under restriction so it wouldn't spin around itself, and be connected to the camera's rotation(?) directly so it would continue to turn correctly when I rotate the player. My latest attempt was with a raycast, but I probably did something incorrrectly there since it doesn't work as welll.

    Any help would be appreciated, hanks!!
     
  2. tree_arb

    tree_arb

    Joined:
    Dec 30, 2019
    Posts:
    323
    Do i understand correctly that you want only the head to look in this specific direction? So as you look around with the camera the head looks but the player doesn't necessarily rotate?

    If so the animation rigging package, or final IK asset would probably be best. Here is a video with head follow w animation rigging package


     
  3. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    142
    I saw this, but the preview package doesn't work really well on my machine and i really don't wanna rely on it. besides, I need to use the camera's rotation to aim the player's attacks as well, so it's kinda necessary
     
  4. tree_arb

    tree_arb

    Joined:
    Dec 30, 2019
    Posts:
    323
    Code (CSharp):
    1.    player.transform.rotation = Camera.main.transform.rotation;
    this will simply set the rotation of the player to the cameras rotation, but it sounds like you want head and body to rotate separately, which i think you will need some form of IK solution. I believe animation rigging package (although might have its issues) is not in preview anymore. I'm using it for humanoid aiming head and arms separate from the body and its working ok. takes some getting use to though.
     
    SassyPantsy likes this.
  5. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    142
    Ok yeah! that's getting there, that's what I mean, but now I'm facing a different problem - I can't prevent the head from spinning around itself, I need to clamp it's values so it would turn in a natural angle. The problem is that I don't know what values should be set.

    I tried writing -

    Code (CSharp):
    1.             if(Camera.main.transform.rotation.y < -0.5f && Camera.main.transform.rotation.eulerAngles.y > -0.5f)
    2.             {
    3.                 playerHead.transform.rotation = Camera.main.transform.rotation;
    4.             }
    But that surprisingly didn't do anything. I tried with other values - x and z, and got: with the x it was visual glitches, with the z it was literally nothing.
     
  6. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    For clamping head rotation you might find transform.localRotation.eulerAngles useful, as you could define +/- max degrees relative to 0 per axis (don't want the head looking down beyond chin either etc.)
     
  7. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    142
    I tried:

    Code (CSharp):
    1. if(playerHead.transform.localRotation.y < 0.5f && playerHead.transform.localRotation.eulerAngles.y > -0.5f)
    2.     {
    3.         playerHead.transform.rotation = Camera.main.transform.rotation;
    4.     }
    And it didn't change anything. the head still spins around itself. what should be the actual syntax?
     
  8. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    You could try something like this..
    Code (CSharp):
    1. playerHead.transform.rotation = Camera.main.transform.rotation;
    2. Vector3 localHeadRotation = playerHead.transform.localRotation.eulerAngles;
    3. localHeadRotation.y = Mathf.Clamp(localHeadRotation.y, min, max);
    4. playerHead.transform.localRotation = Quaternion.Euler(localHeadRotation);
    Edit: where "min" and "max" might be something like -45 and 45.. assuming the head has a Y rotation of 0 when facing forward. And you'd probably want to clamp .x as well, with whatever freedom of movement you're allowing for the head looking up and down.
     
    Last edited: Mar 28, 2021
  9. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    142


    Getting real close now. The issue right now:
    Clamping these values - both x and y are needed to be restrained after all - only causes the head to snap to each value. For example, if the values are clamped between -45 and 45, moving the camera left and right snaps to each of these values, instead of freely moving between them. That's a really weird outcome for clamping. Any ideas?
    Also, how did you learn to manipulate Euler angles? I always get lost trying to figure them out.

    Edit: oh I didn't see your edit before replying 0_0
     
  10. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    You could add a Debug.Log with the localHeadRotation.y before you've clamped it, to see what the value is. There's a good chance it's because looking in one direction is actually going 0,359,358.. so could add a conditional before the clamp if that's the case, like
    if (y > 180) y -= 360;
    . Also good to get familiar with Mathf.SmoothDampAngle (for soft transitions between angles over time) and Mathf.LerpAngle. Both of these handle the 0->359 issue themselves, vs. normal SmoothDamp or Lerp. Another possibility is that your head doesn't have a rotation equivalent to Quaternion.identity, and it's been set up backwards or something, but that's less likely.
    Edit: minor change to something in the above
     
  11. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    I don't remember, originally I probably learned about eulers from seeing usage, or searching for an answer about how to do something that had a solution using them. I'm not educated in complicated math beyond like basic algebra. I mostly have to rely on people that know more than me about it for anything much beyond that. Like I have no idea what's a cross product, dot product, totally lost with that kind of thing :( but a snippet written by someone that does know that stuff is just as good as if I did know and wrote it myself.. at least that's always the hope :)
     
  12. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    142
    I've actually used this to make the character turn towards the camera when he starts walking and attacking. Took it from the Brackey's tutorial but he's not explaining it that much so I couldn't replicate it in this case. I'll look into that more though, since it might actually be the solution I need.

    It does go from 0 to 359 etc as you said, but that if statement didn't help... It's hard to explain what it did tbh, but it still snaps - just less 'easily'.
    I did simply write
    if (localHeadRotation.y > 180f) localHeadRotation.y -= 360f;
    if (localHeadRotation.x > 180f) localHeadRotation.y -= 360f;
    before clamping, and I have no idea of the syntax (heh...) so that might be the problem.

    Yeah... I think I can relate to that
     
  13. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    You could add a dummy transform as a sibling of the head (i.e. just an empty GameObject with the same parent as the head), apply your rotation then local rotation clamping to that instead of the head directly like you're doing now, then use SmoothDampAngle like below along with a
    private float playerHeadRXVel;
    and
    private float playerHeadRYVel;
    ..
    Code (CSharp):
    1.  
    2. // first set your dummyHead object rotation the same as you were with playerHead, then..
    3. Vector3 currentRotation = playerHead.transform.localRotation.eulerAngles;
    4. Vector3 targetRotation = dummyHead.transform.localRotation.eulerAngles;
    5. Vector3 finalRotation =  new Vector3(
    6.   Mathf.SmoothDampAngle(currentRotation.x, targetRotation.x, ref playerHeadRXVel, 0.5f),
    7.   Mathf.SmoothDampAngle(currentRotation.y, targetRotation.y, ref playerHeadRYVel, 0.5f)
    8. );
    9. playerHead.transform.localRotation = Quaternion.Euler(finalRotation);
    That way the head rotation should continuously transition to the new rotation within half a second (the 0.5f). You could then raise or lower this value to change how long it takes until it "looks right."
    Edit: fixed something dumb in the above :D
     
    SassyPantsy likes this.
  14. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    142
    For some reason I only saw this now (also moved on to some other stuff) but I tried it (and some variations on it) and the direct result was a furious nod of the head up and down, no matter what the actual camera's rotation was lol.

    so first of all, here's the code:
    Code (CSharp):
    1. //sets the headDummy's rotation to the camera's.
    2.             headDummy.localRotation = Camera.main.transform.localRotation;
    3.             //create a vector that's based on the current local rotaion of the dummyHead
    4.             Vector3 localDummyRotation = headDummy.localRotation.eulerAngles;
    5.  
    6.             //change the dummyHead's values if it reacheas the edge of it's rotation
    7.             if (localDummyRotation.y > 180f) localDummyRotation.y -= 360f;
    8.             if (localDummyRotation.x > 180f) localDummyRotation.y -= 360f;
    9.  
    10.             //clamp the localRotation to values that look natural on a head
    11.             localDummyRotation.x = Mathf.Clamp(localDummyRotation.x, 0.5f, -0.5f);
    12.             localDummyRotation.y = Mathf.Clamp(localDummyRotation.y, 0.5f, -0.5f);
    13.  
    14.             //set the headDummy rotation to the newly modified localDummyRotation, which is also the target rotation angle.
    15.             headDummy.localRotation = Quaternion.Euler(localDummyRotation);
    16.  
    17.             Vector3 currentRotation = playerHead.localRotation.eulerAngles;
    18.  
    19.             //uses the two angles to create a "transition" angle so as to make the actuall head changes appear smoother.
    20.             Vector3 finalRotation = new Vector3(Mathf.SmoothDampAngle(currentRotation.x, headDummy.localRotation.x, ref playerHeadRXVel, headTurnSmoothTime),
    21.                   Mathf.SmoothDampAngle(currentRotation.y, headDummy.localRotation.y, ref playerHeadRYVel, headTurnSmoothTime));
    22.  
    23.             //apllies the smooth angle to the head itself.
    24.             playerHead.localRotation = Quaternion.Euler(finalRotation);

    to give a better understanding, looking from the scene vies, the hedDummy's rotation is in a wonky position, and is static. The playerHead is snapping up and down between it's clamping angles, and doesn't turn right or left.

    Now, if I don't clamp anything, then the head rotates nice and smoothly, however it still turns around itself.
     
  15. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    142
    One thing I just did right now was to remove the clamping part from the dummyHead, so that the dummyHead spins around freely.

    Then I created a new vector3 that will be the actual targetAngle (without the smoothing thingy), that angle I clamped, and I assigned the head's rotation to that!

    And it still snaps from the clamping points.
    I think angles don't clamp like normal vectors, and it messes them up. There must be a different way to clamp angles than there is to clamp positions, and that's what I'm gonna look up now.
     
  16. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    I just did something quick to get you started. For this example I created a new project, URP template.

    Code (CSharp):
    1. using UnityEngine;
    2. public class HeadRotationExample : MonoBehaviour
    3. {
    4.   [Tooltip("Set to any Transform, or null to look forward relative to the camera.")]
    5.   public Transform lookAt;
    6.   public float rotationSpeed = 0.25f;
    7.   public Vector4 limitLeftRightUpDown = new Vector4(-80, 80, -45, 45);
    8.   private float rxVel = 0;
    9.   private float ryVel = 0;
    10.   void Update()
    11.   {
    12.     Vector3 forward = (lookAt == null) ? Camera.main.transform.forward : (lookAt.position - transform.position).normalized;
    13.     Vector3 prevRotation = transform.localRotation.eulerAngles;
    14.     transform.forward = forward;
    15.     Vector3 newRotation = transform.localRotation.eulerAngles;
    16.     if (newRotation.x > 180) newRotation.x -= 360;
    17.     if (newRotation.y > 180) newRotation.y -= 360;
    18.     newRotation.x = Mathf.Clamp(newRotation.x, limitLeftRightUpDown.z, limitLeftRightUpDown.w);
    19.     newRotation.y = Mathf.Clamp(newRotation.y, limitLeftRightUpDown.x, limitLeftRightUpDown.y);
    20.     transform.localRotation = Quaternion.Euler(new Vector3(
    21.       Mathf.SmoothDampAngle(prevRotation.x, newRotation.x, ref rxVel, rotationSpeed),
    22.       Mathf.SmoothDampAngle(prevRotation.y, newRotation.y, ref ryVel, rotationSpeed)
    23.     ));
    24.   }
    25. }
    neck_with_component.jpg

    1. Right-click Props in the Hierarchy, select Prefab->Unpack Completely
    2. Create a 'Neck' GameObject as a child of the Jigsaw.
    3. Drag 'Safety Hat' onto Neck to make it a child.
    4. Zero the hat's position and rotation, then move it up a bit.
    5. Create a new C# file, name it HeadRotationExample.
    6. Click the Neck, Add Component, select the HeadRotationExample component.
    7. Hit Play. Move/Look around.

    Also added a "Look At" property you can assign another Transform to. To illustrate..
    1. Click Props->Paint Supplies->Paint 5G Bucket. Uncheck "static" so you can move it around during Play Mode.
    2. Drag it to the "Look At" property of the component.
    3. Hit Play. Select the bucket in Scene view, move it around.

    The "Rotation Speed" property is how fast the head rotation transitions.

    The "Limit Left Right Up Down" property allows you to limit the 4 directions by number of degrees.

    In practice you could attach the component to a neck bone. You would also change Update to FixedUpdate if you're using physics :)
     
    SassyPantsy likes this.
  17. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    142

    Well I can understand the logic in the code, but it still doesn't work.

    On the URP project it does work by it's own logic (although inverted, but easily fix-able), on my own project it doesn't work at all:
    If the script's attached to the neck, then the head itself jitters. if the script's attached to the head, then the head keeps on looking forward, regardless of the camera's angle or whatever animation's playing.

    Is it possible that part of the problem is the fact that the camera is controlled by a cinemachine mind? there's still a Camera.main that the script references, but maybe that's an issue?

    Anyway, I don't think the camera's angle is the issue, after all it can be achieved by a Raycast - no matter the camera. The issue is the sole fact that the head spins around itself. I think once we could find the calculation that limits the rotation, and reset's it's rotation values once it reaches a certain threshold, then that could work. Is there another way to do that besides clamping?

    By the way, thanks a lot for your involvement in this man! It's also quite funny how something that looks so easy to make is proving to be my greatest coding challenge yet haha
     
    adamgolden likes this.
  18. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    Hopefully the example helped somewhat - I don't know what else to suggest? You could try putting code in LateUpdate, maybe something else is applying changes in Update after your script for that frame. Also again, if you're controlling the character with physics you would probably want to have your code in FixedUpdate.

    Personally, to avoid a number of jitter-relates issues I have a function in my camera controller called FinalizeUpdate, which I execute each frame after all other scripts that might influence the camera have completed. This in turn calls a function on whichever script references I need to execute logic in after all other camera motion has been applied for the frame (user interaction, smoothing, blending etc.). If you did something like that, you could call the head rotation script from there to be sure nothing else will change it until the next frame.
     
  19. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    142
    Yeah I'll think I'll get back to it when other things get more finalized. Even though this makes a big effect for polish and the general vibe of the game, this is still a cosmetic.

    I'm really interested though how you managed to create a method that gets executed after everything else,

    Like what's the syntax? How do you make sure whatever's in it gets executed last?
     
  20. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    There is this:
    https://docs.unity3d.com/Manual/class-MonoManager.html

    I haven't needed to bother with the above (so far).. I just take advantage of the existing scene iteration. The engine goes down the list, and for each item, the children get processed, and then the components on the object itself do. So whatever is lower in the list at the same level (i.e. among siblings) runs later.

    A minimal example would be having 2 GameObjects at the root of your scene, with all of your normal scene content as a child of the first one. Then you can put whatever script on the second GameObject and an Update in that will always run after every script of the first GameObject (and children) is done their Update for that frame. So if you added a script with a LateUpdate to the second GameObject, it would run after every other object in the scene has finished FixedUpdate, Update and LateUpdate calls for the frame.

    However, do keep in mind that all LateUpdate calls happen after all Update calls, regardless of their order in the hierarchy.
    https://docs.unity3d.com/Manual/ExecutionOrder.html
     
    SassyPantsy likes this.
  21. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    142
    Niice man. Thanks.
     
    adamgolden likes this.
  22. MartinMa_

    MartinMa_

    Joined:
    Jan 3, 2021
    Posts:
    455
    Why dont just add camera component to head gameobject and just set LookAt function to other camera? :)U can use that head camera for first person.
     
    SassyPantsy likes this.
  23. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    142
    You mean another camera that the head looks at? That'll mean a new script that moves the other camera wouldn't it?
     
  24. MartinMa_

    MartinMa_

    Joined:
    Jan 3, 2021
    Posts:
    455
    No you add camera component to your playerHead game object and set first camera as target of this camera.
    Code (CSharp):
    1. public GameObject camera1
    2.  
    3.  
    4. var camera2 = gameObject.GetComponent(camera).transform;
    5. gameObject.LookAt(camera1)
    You can use LookAt with any GameObject what have Camera component.
     
    SassyPantsy likes this.
  25. SassyPantsy

    SassyPantsy

    Joined:
    May 17, 2020
    Posts:
    142
    I'm sorry man I really don't understand...
    A. Why would I add another camera just to use the lookAt() function? I can do that with the main camera already, the problem is that the head rotates 360, I.e there's no way to clamp the head values so it won't rotate around itself.
    B. I don't understand which camera is supposed to look at which, and which camera is supposed to be under what gameObject...

    BTW this isn;t an FPS conotroller it's a TPS one
     
  26. blizstudio

    blizstudio

    Joined:
    Nov 10, 2017
    Posts:
    2
    If anyone is still needing help on this, if you are using the Brackey's tut and the Cinemachine Free Look camera, then you can just take the Target, make it a child of the Main Camera and then add +10 to Z axis. Boom, your character upper body/head turns with the Free Look camera.
     
  27. binbbaz

    binbbaz

    Joined:
    Oct 20, 2022
    Posts:
    3
    I was thinking of why we need to write a script to do this task when I came across this solution. Wouldn't attaching the target as the child of the main camera suffice? Unfortunately, I tried it and didn't work. Could you please explain this your solution further probably with a screen shot. I'm new to Avatar rigging and I have just added Cinemachine to my project from unity asset store