How can I limit the angle rotation look at ?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestRot : MonoBehaviour
{
    public Transform target;
    public float speed = 1.0f;

    void Update()
    {
        Vector3 targetDirection = target.position - transform.position;
        float singleStep = speed * Time.deltaTime;

        Vector3 newDirection = Vector3.RotateTowards(transform.forward, targetDirection, singleStep, 0.0f);

        float angle = Quaternion.Angle(transform.rotation, target.rotation);

        if (angle < 60)
        {
            transform.rotation = Quaternion.LookRotation(newDirection);
        }
    }
}

I tried to use float angle and then to check if it’s not smaller then 60 for example.
I want to limit the x and y angles to -60 and 60 for example so the object will not rotate 360 degrees.

Here is a short video clip I recorded showing the problem when the cube is behind the player and that the player head rotation speed is not the same speed as the cube so in most of the time the player is not looking at the cube but looking at it in general direction .

So let’s say you have a forward transform. Your player rotates the camera past where you want to limit the angle(in your case 60 degrees).

Get the angle between the forward you want to stick too, and what the new rotation would be. So let’s say its 90 degrees the player tried to rotate, but you want to limit him to 60.

Now you need to Slerp between the locked forward vector and the full rotation by a set amount. Remember that Slerp and Lerp functions take values between 0 and 1. You need the ratio to Slerp from the locked vector to the attempted vector. In this case its 60/90.

1 Like

I tried this using clamp but still it’s not working :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotateToTarget : MonoBehaviour
{
    public Transform target;
    public float speed = 1.0f;

    void Update()
    {
        // Determine which direction to rotate towards
        Vector3 targetDirection = target.position - transform.position;

        // The step size is equal to speed times frame time.
        float singleStep = speed * Time.deltaTime;

        // Rotate the forward vector towards the target direction by one step
        Vector3 newDirection = Vector3.RotateTowards(transform.forward, targetDirection, singleStep, 0.0f);

        transform.eulerAngles = new Vector3(ClampAngle(transform.eulerAngles.x, transform.eulerAngles.x - 60, transform.eulerAngles.x + 60), 0, 0);
        transform.eulerAngles = new Vector3(0, ClampAngle(transform.eulerAngles.y, transform.eulerAngles.y - 100, transform.eulerAngles.y + 100), 0);

        // Calculate a rotation a step closer to the target and applies rotation to this object
        transform.rotation = Quaternion.LookRotation(newDirection);
    }

    private float ClampAngle(float angle, float min, float max)
    {
        if (angle < 90 || angle > 270)
        {       // if angle in the critic region...
            if (angle > 180) angle -= 360;  // convert all angles to -180..+180
            if (max > 180) max -= 360;
            if (min > 180) min -= 360;
        }
        angle = Mathf.Clamp(angle, min, max);
        if (angle < 0) angle += 360;  // if angle negative, convert to 0..360
        return angle;
    }
}

Here is a short video clip I recorded showing the problem when the cube is behind the player and that the player head rotation speed is not the same speed as the cube so in most of the time the player is not looking at the cube but looking at it in general direction .

If your look at is for a characters head, i would actually recommend using ik, even unities built in ik, to make it much simpler. It should be pretty trivial to setup for a humanoid character, and you could control the weight of the look at ik so that it can’t twist all the way around(i usually use the dot product of the characters forward vector and the direction to the look at target, clamping it to a 0 to 1 range, and feed that number to the look at ik weight. this will make sure the ik weight falls to 0 as it reaches 90 degrees).

You could take a look at this page :

That being said, what I was refering to earlier would have been something more like this:

using UnityEngine;

public class RotationTest : MonoBehaviour
{
    public Transform lookAt;
    public Vector3 defaultFacing;
    public float angleLimit = 60f;
    public float rotationDamping = 3f;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Using late update so animations don't overwrite my transform manipulations...
    void LateUpdate()
    {
        // For this example i'll be using the root objects forward vector as what i want to be my reference constraint vector.
        // You might want to use something else though.
        defaultFacing = transform.root.forward;

        // The direction from this transform, pointing at the look at target.
        Vector3 directionToLookAtTarget = lookAt.position - transform.position;

       
        float angle = Vector3.Angle(directionToLookAtTarget, defaultFacing);

        // Since i'm just using the root objects forward vector as a constraint, i can just use its rotation as my default rotation instead of calculation a Quaternion.LookAt.
        Quaternion defaultRotation = transform.root.rotation;
        // The look at rotation to the target if it were completely unrestrained.
        Quaternion lookAtCompleteRotation = Quaternion.LookRotation(directionToLookAtTarget);

        Quaternion finalRotation = Quaternion.identity;

        // If the angle is greater than our limit, return a rotation that is in the direction of the lookAtCompleteRotation but is limited to the angle we chose as a limit.
        // Otherwise, if its within our limit, we just return the rotation as is.
        if (angle > angleLimit)
            finalRotation = Quaternion.Slerp(defaultRotation, lookAtCompleteRotation, angleLimit / angle);
        else
            finalRotation = lookAtCompleteRotation;

            transform.rotation = Quaternion.Slerp(transform.rotation, finalRotation, Time.deltaTime * rotationDamping);
    }
}
2 Likes

Working fine it’s just if I want the defaultFacing to be forward ? Now it’s looking to the sides when the cube is behind.
I tried :

defaultFacing = transform.forward;

And also

defaultFacing = Vector3.forward;

but it’s not working the head is not facing forward.

I tried to use the IK idea but the problem is when setting the weight to 0 it’s settings it too fast. The head is like jumping from looking at mode to looking forward mode. I tried to use in Update somehow to change the weight from 1 to 0 slowly but it’s not working.

using UnityEngine;
using System;
using System.Collections;
using UnityEngine.SocialPlatforms;
using UnityEditor.Animations;

[RequireComponent(typeof(Animator))]

public class IKControl : MonoBehaviour
{
    protected Animator animator;

    public bool ikActive = false;
    public Transform headObj = null;
    public Transform lookObj = null;

    private bool changeWeight = false;
    private const float LERP_TIME = 1f;
    private float currentLerpTime;

    void Start()
    {
        animator = GetComponent<Animator>();
    }

    //a callback for calculating IK
    void OnAnimatorIK()
    {
        if (animator)
        {
            changeWeight = false;

            //if the IK is active, set the position and rotation directly to the goal. 
            if (ikActive)
            {
                changeWeight = false;

                // Set the look target position, if one has been assigned
                if (lookObj != null)
                {
                    animator.SetLookAtWeight(1, 1, 1, 1, 1);
                    animator.SetLookAtPosition(lookObj.position);
                }
            }

            //if the IK is not active, set the position and rotation of the hand and head back to the original position
            else
            {             
                    changeWeight = true;             
            }
        }
    }

    private void Update()
    {
        if (changeWeight)
        {
            currentLerpTime += Time.deltaTime;
            float lerpProgress = currentLerpTime / LERP_TIME;
            Quaternion.Lerp(headObj.rotation, Quaternion.Euler(new Vector3(0,0,0)), Mathf.Clamp01(lerpProgress));
        }
    }
}

No matter what have I tried in the Update it’s never changing the weight from 1 to 0 slowly smooth.
It’ changing from 1 to 0 at once.

Now I’m just trying to rotating back the head to the default direction but it’s not working.
When changing the flag ikActive while the game is running to false the code is the Updae has no affect. It’s changing it from 1 to 0 at once.

I tried in the update to lerp the animator.SetLookAtWeight but nothing is working.
Maybe there is a way and I’m doing it wrong.

There is one potential problem with the code i linked. It will work if your heads transform forward is the same as unitys transform forward. this is not often the case with rigged models unfortunately. if you select the head bone, and tell the unity editor to show an objects local transform, you want to make sure the head bones transform isn’t out of wack. And yes, the script will also have the character rotate his head as far as he can to look at the object within the angle limit.

As for the character IK script, which is what you should probably use for characters anyway, the reason the weight is changing to 1 or 0 instantly is because its up to you to smooth out the value that your passing into the function. I also found the script you just linked a little weird(no offense), so try something like this and tell me if this is what you were looking to achieve:

using UnityEngine;
[RequireComponent(typeof(Animator))]

public class LookAtIKController : MonoBehaviour
{
    public Transform lookObj = null;
    public float finalLookWeight;
    public float weightDamping = 1.5f;

    protected Animator animator;

    void Start()
    {
        animator = GetComponent<Animator>();
    }

    //a callback for calculating IK
    void OnAnimatorIK()
    {
        if (lookObj != null)
        {
            Vector3 flattenedLookAtVector = Vector3.ProjectOnPlane(lookObj.position - transform.position, transform.up);
            float dotProduct = Vector3.Dot(transform.forward, flattenedLookAtVector);
            float lookWeight = Mathf.Clamp(dotProduct, 0f, 1f);
            finalLookWeight = Mathf.Lerp(finalLookWeight, lookWeight, Time.deltaTime * weightDamping);
            float bodyWeight = finalLookWeight * .5f;

            animator.SetLookAtWeight(finalLookWeight, bodyWeight);
            animator.SetLookAtPosition(lookObj.position);
        }
    }
}
1 Like

Yes, this is working great.

Thank you very much.

What if I want to make it possible also that the player will rotate 360 degrees the head and body if needed when the cube is behind him in every direction not only in the front view ? What should I change ? I want to check this
behavior too.