4
votes

I have a game object that contains another game object that should be able to rotate towards target (imagine a tank turret). And so I've created the below script:

public class Rotator : MonoBehaviour {

    public GameObject _enemy;

    void Update () {
        var actualTarget = _enemy.transform.position;
        var targetDir = actualTarget - transform.position;
        var step = 2 * Time.deltaTime;
        var target = Quaternion.LookRotation(targetDir.normalized, Vector3.up);
        var actual = target * Quaternion.Inverse(transform.parent.rotation);
        var targetRotation = Quaternion.Slerp(transform.localRotation, actual, step);

        targetRotation.eulerAngles = ClampRotation(targetRotation.eulerAngles);
        transform.localRotation = targetRotation;
    }

    private static Vector3 ClampRotation(Vector3 eulerAngles) {
        var x = Mathf.Clamp(eulerAngles.x > 180 ? eulerAngles.x - 360 : eulerAngles.x, -180, 180);
        var y = Mathf.Clamp(eulerAngles.y > 180 ? eulerAngles.y - 360 : eulerAngles.y, -45, 45);

        return new Vector3(x, y, 0);
    }

}

Objects setup:

enter image description here

The rotation of the object named "parent" is 90deg on the Y axis, everything else is not rotated.

Clamping on the y axis works well - the rotation stays between -45 and 45 degrees. The rotation however doesn't work on the x axis (with, or without clamping). So the goal here is that when I move the cube left or right, the red one rotates between [-45,45] degrees around the Y axis and when I move it top or down, the red one rotates between [-180,180] degrees around the X axis. I had some success using the LookAt method of the Transform clas, but for some reason if I try to manually modify eulerAngles of a localRotation it suddenly looses the possibility to rotate backwards on the X axis even though I'm only doing something to the Y values...

enter image description here

1
I think there is no answer yet because people are not clear on what the problem is. You say it rotates but what's the problem now? Any animated gif to show what's going on now and what you expect?Programmer
Is it clearer now?Marek M.
Yes, it doesn't move up. You also want to be able to add rotation limit for both axis?Programmer
If by moving up you mean that the red guy is not pointing at the moving cube when it moves up/down, then yes - that's the problem. And again - yes - I want to add rotation limit for X and Y axis. The Z one I'm just setting to 0, as I don't want it to be influenced at all.Marek M.

1 Answers

2
votes

After long hours of trial and error and browsing the internet frenzily, I managed to find an answer that I could tailor to my needs. Word of comment to the first if statement of the Clamp method - this is useful if I want the object to also be clamped in it's inverted position (if the target is behind it):

void Update() {
    transform.LookAt(_target.transform);

    var rotation = transform.localRotation;
    var eulers = ClampRotation(rotation.eulerAngles);

    transform.localEulerAngles = eulers;
}

private static Vector3 ClampRotation(Vector3 eulerAngles) {
    var x = Clamp(eulerAngles.x, -60, 60);
    var y = Clamp(eulerAngles.y, -45, 45);

    return new Vector3(x, y, 0);
}

private static float Clamp(float angle, float min, float max) {
    if ((angle <= 180 && angle >= 180 - Mathf.Abs(min)) || (angle >= 180 && angle <= 180 + max)) {
        return Mathf.Clamp(angle, 180 - Mathf.Abs(min), 180 + max);
    }

    if (angle > 180f) {
        angle -= 360f;
    }

    angle = Mathf.Clamp(angle, min, max);

    if (angle < 0f) {
        angle += 360f;
    }

    return angle;
}

EDIT:

As it turns out, sometimes it's better to create your own fine-grained solution, you can modify more easily, so to anyone who is interested, you can do what I wanted to do with the below code as well:

void Update() {
    var actualTarget = _enemy.transform.position;
    var targetDir = actualTarget - transform.position;
    var target = Quaternion.LookRotation(targetDir.normalized, transform.up);
    var actual = Quaternion.Inverse(transform.parent.rotation) * target;

    actual.eulerAngles = ClampRotation(actual.eulerAngles);

    var targetRotation = Quaternion.Slerp(transform.localRotation, actual, 8 * Time.deltaTime);

    transform.localRotation = targetRotation;
}

private static Vector3 ClampRotation(Vector3 newRotation) {
    var x = Clamp(newRotation.x, -179, 179);
    var y = Clamp(newRotation.y, -45, 45);

    return new Vector3(x, y, 0);
}

private static float Clamp(float angle, float min, float max) {
    if ((angle <= 180 && angle >= 180 - Mathf.Abs(min)) || (angle >= 180 && angle <= 180 + max)) {
        return Mathf.Clamp(angle, 180 - Mathf.Abs(min), 180 + max);
    }

    if (angle > 180f) {
        angle -= 360f;
    }

    angle = Mathf.Clamp(angle, min, max);

    if (angle < 0f) {
        angle += 360f;
    }

    if (Mathf.Abs(angle) == 360) {
        angle = 0;
    }

    return angle;
}