Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question Object rotation clamping.

Discussion in 'Scripting' started by Steve_UK, Jun 29, 2020.

  1. Steve_UK

    Steve_UK

    Joined:
    Oct 13, 2014
    Posts:
    3
    Hi, I am trying to limit the rotation of a gun turret to a 180 degree arc on the Y axis, like so:

    My code:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class LauncherController : MonoBehaviour
    4. {
    5.   [SerializeField] private float rotationSpeed = 5.0f;
    6.  
    7.   private float traverse;
    8.   private float bearing;
    9.  
    10.   void Update()
    11.   {
    12.     traverse = Input.GetAxis("Horizontal") * rotationSpeed;
    13.     transform.Rotate(new Vector3(0, traverse, 0));
    14.  
    15.     bearing = transform.rotation.eulerAngles.y;
    16.     bearing = Mathf.Clamp(bearing, -90.0f, 90.0f);
    17.     transform.rotation = Quaternion.Euler(0.0f, bearing, 0.0f);
    18.   }
    19. }
    20.  

    The problem I'm having, is that the turret only moves to the right (from 0 to +90). Any attempt to move the turret to the left of 0 causes it to spring back to +90:




    Please steer me in the right direction!
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,735
    Once you feed your rotation into the transform and then try to get it back out, the rotation will be normalized to the range of 0-360. So for example if you give it a rotation of -10, and then read the value out again, it will come back as 350.

    One solution is to keep your own rotation variable a use that as the source of truth instead of the transform's rotation:

    Code (CSharp):
    1. float currentRotation;
    2.  
    3. void SetCurrentRotation(float rot) {
    4.   currentRotation = Mathf.Clamp(rot, -90, 90);
    5.   transform.rotation = Quaternion.Euler(0, rot, 0);
    6. }
    7.  
    8. void Update() {
    9.   float newRotation = currentRotation + Input.GetAxis("Horizontal") * rotationSpeed;
    10.   SetCurrentRotation(newRotation);
    11. }
    By the way you should probably incorporate Time.deltaTime into the rotation code, otherwise your rotation will look choppy and be framerate-dependent:

    Code (CSharp):
    1. float newRotation = currentRotation + Input.GetAxis("Horizontal") * rotationSpeed * Time.deltaTime;
    You may have to increase the "rotationSpeed" variable when you do this. You can now think of it as "degrees per second". A value like 100 may be appropriate. Tweak it until it feels right.
     
    Last edited: Jun 29, 2020
    Steve_UK likes this.
  3. Steve_UK

    Steve_UK

    Joined:
    Oct 13, 2014
    Posts:
    3
    This worked beautifully, thank you!

    I had an idea it was related to normalization, but couldn't come up with a solution :confused:

    Regarding Time.deltaTime, the Input.GetAxis information provided by Unity states: "This is frame-rate independent; you do not need to be concerned about varying frame-rates when using this value." So I was unsure. I have added it now!
     
  4. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,735
    Hmm actually you might be right about the deltaTime thing now that I'm looking at it. Maybe try both and just see which feels better!
     
    Steve_UK likes this.
  5. WarmedxMints

    WarmedxMints

    Joined:
    Feb 6, 2017
    Posts:
    1,035
    Here's something I use to restrict angles on a given axis;
    Code (CSharp):
    1.     public enum Axis
    2.     {
    3.         X = 0,
    4.         Y = 1,
    5.         Z = 2
    6.     }
    7.  
    8.     public float Sensitvity = 2f;
    9.  
    10.     [SerializeField]
    11.     private Axis _axis = Axis.Y;
    12.  
    13.     [SerializeField]
    14.     private float _rotationSpeed = 5f;
    15.  
    16.     [SerializeField]
    17.     private float _minAngle = -90f,
    18.                     _maxAngle = 90f;
    19.  
    20.     private Quaternion _targetRot;
    21.     private Vector3 _rotationAxis = Vector3.zero;
    22.  
    23.     private void Start()
    24.     {
    25.         _targetRot = transform.rotation;
    26.     }
    27.  
    28.     private void Update()
    29.     {
    30.         var input = Input.GetAxis("Horizontal") * Sensitvity;
    31.  
    32.         _rotationAxis[(int)_axis] = input;
    33.  
    34.         _targetRot *= Quaternion.Euler(_rotationAxis);
    35.  
    36.         _targetRot = ClampAngleOnAxis(_targetRot, (int)_axis, _minAngle, _maxAngle);
    37.  
    38.         transform.rotation = Quaternion.Lerp(transform.rotation, _targetRot, Time.deltaTime * _rotationSpeed);
    39.     }
    40.  
    41.     private Quaternion ClampAngleOnAxis(Quaternion q, int axis, float minAngle, float maxAngle)
    42.     {
    43.         q.x /= q.w;
    44.         q.y /= q.w;
    45.         q.z /= q.w;
    46.         q.w = 1.0f;
    47.  
    48.         var angle = 2.0f * Mathf.Rad2Deg * Mathf.Atan(q[axis]);
    49.  
    50.         angle = Mathf.Clamp(angle, minAngle, maxAngle);
    51.  
    52.         q[axis] = Mathf.Tan(0.5f * Mathf.Deg2Rad * angle);
    53.  
    54.         return q;
    55.     }
     
    Steve_UK likes this.
  6. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,748
    Wow, that is a....very poorly phrased sentence. Clearly what they intended to mean is "This returns a constant '1' whether the framerate is 20fps or 200 fps", but if I were a new user I would clearly just see the "you do not need to be concerned about..." and ignore the parts of the sentence I didn't understand assuming I didn't need to worry about it. When in a lot of Unity code, you DO need to worry about it, because a lot of the time you do need to adjust that for the framerate!

    Basically, if you're setting a value and not adding to it (e.g. setting rigidbody.velocity), don't multiply Time.deltaTime; if you're changing an absolute value each frame like .position or angles, you do need it. In this code you do need it.

    (And this is made more confusing by the occasional exceptions; for example, mouse input which is treated as an axis is the absolute amount of movement the mouse has moved, and thus should not be multiplied by Time.deltaTime no matter the context.)
     
    Steve_UK and PraetorBlue like this.
  7. Steve_UK

    Steve_UK

    Joined:
    Oct 13, 2014
    Posts:
    3
    Thanks for the input, this what I have:





    My final code:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class TurretController : MonoBehaviour
    4. {
    5.   [SerializeField] private Transform gun = null;
    6.   [SerializeField] private Transform turretBase = null;
    7.  
    8.   [SerializeField] private float rotationSpeed = 5.0f;
    9.  
    10.   private float traverse;
    11.   private float currentBearing;
    12.   private float newBearing;
    13.  
    14.   private float elevate;
    15.   private float currentElevation;
    16.   private float newElevation;
    17.  
    18.   void Update()
    19.   {
    20.     traverse = Input.GetAxis("Horizontal") * rotationSpeed * Time.deltaTime;
    21.     turretBase.transform.Rotate(0, traverse, 0);
    22.     newBearing = currentBearing + traverse;
    23.     SetCurrentBearing(newBearing);
    24.  
    25.     elevate = Input.GetAxis("Vertical") * rotationSpeed * Time.deltaTime;
    26.     gun.transform.Rotate(elevate, 0, 0);
    27.     newElevation = currentElevation + elevate;
    28.     SetCurrentElevation(newElevation);
    29.  
    30.   }
    31.  
    32.   void SetCurrentBearing(float rot)
    33.   {
    34.     currentBearing = Mathf.Clamp(rot, -50, 50);
    35.     turretBase.transform.rotation = Quaternion.Euler(0, rot, 0);
    36.   }
    37.  
    38.   void SetCurrentElevation(float rot)
    39.   {
    40.     currentElevation = Mathf.Clamp(rot, -50, 0);
    41.     gun.transform.rotation = Quaternion.Euler(rot, currentBearing, 0);
    42.   }
    43.  
    44. }
    45.  
    Everything seems to work nicely. I may put the code in Update into two new functions, as there will be a firing functionality added and things might get messy. Are there any glaring issues here?
     
    wmadwand likes this.
  8. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,735
    I think splitting the code you have in Update into a few new functions is a great idea to unclutter Update(), especially if you're going to add more code to it.

    I don't think you need lines 21 and 26 at all and can safely delete them. The rotation gets overriden in the UpdateXXX functions later anyway.

    The other minor thing I would say is that aside from
    currentBearing
    and
    currentElevation
    , I don't think you need any of the other variables to be instance variables. They can all be local variables. That could help declutter your instance variable area at the top of the class.

    Otherwise looking good!
     
    StarManta and Steve_UK like this.