Having trouble checking my rotational values.

I'm trying to rotate an object and check when it's rotated a certain amount so it knows when to stop. I have a playable character that banks left while you hold down left, and banks right when you hold down right, but they need to stop once they bank about 10 degrees in either direction.

This is all pretty easy logically until you factor in that rotations "wrap around" and go from 360 back to 0, so now I'm a little confused about how to actually do this.

I was going to post my code, but none of it works the way I originally planned it, so any help would be greatly appreciated as I'm back to square one.

As a quick update to what I've tried, I also tried doing a simple check such as "if Z rotation is less than 10, increase, if greater than 350, decrease" but this again causes problems with the wrapping.

So, by bank, you mean a Z-rotation? When you bank, are you also turning that direction? Side-slipping (not changing Y-angle, but sliding a little sideways as you move forwards?) Bank+turn or move can drive you into the ground if you aren't careful.

Right, z-rotation. There's no other turning happening, just a z-rotation.

5 Answers

5

Probably better to maintain a separate roll offset value in the player [-10 to +10] and then just set the roll with this value wrapped.

I suppose... I was mostly worried that somehow the variable and the actual rotation angle might get out of sync (somehow). I'll try doing something similar.

If you set the rotation to -10, then look it up, it might say 350, so DON'T LOOK IT UP. Code looks like: if(Input.GetKey("a")) { zAng-=0.1; if(zAng<-10) zAng=-10; } transform.eulerAngles=Vector3(0,0,zAng); To "synch," only ever look at and change zAng, and set "real" rotation from it last.

Maybe this is a starting point for you? I haven't tested the code though.

public var maxRotationDelta : float = 10;
public var speed : float = 45.0;
private var initialAngleY : float;
private var bankLeft : boolean;
private var angleY : float;

function Start()
{
 initialAngleY = transform.eulerAngles.y;
}
function Update () {

if(bankLeft)
{
    maxRotationDelta = maxRotationDelta + 180;
}
    angleY  = Mathf.MoveTowardsAngle(transform.eulerAngles.y, initialAngleY + maxRotationDelta, speed * Time.deltaTime);
    transform.eulerAngles = Vector3(0, angleY, 0);
}

Thanks for that. Didn't even know about MoveTowardsAngle, might help in coming up with a solution. Once I do, I'll be sure to post what I did in my question.

You could just maintain a separate variable which is the amount of rotation you have added so far. Then just check this variable instead of transform.rotation.

Actually, I think you have that backwards: a Dot product of 1 means parallel, and 0 means orthogonal. (BTW: I'm making a flight sim too!)

If there's no other rotation (and even if there is) this should be pretty simple. What works for me is using the input axes. Let's say you're using the "Horizontal" (A/D/Left/Right) keys:

var maxBankAngle: float = 10;
var bankInput: float = 0;
var tempZRot: float = 0;
var refTransform: Transform = transform;

function Update()
{
    // store the axis' value in case we want it later
    bankInput = Input.GetAxis("Horizontal"); // returns between -1 and 1
    tempZRot = maxBankAngle * bankInput; // between -10 and 10
    if (tempZRot &lt 0)
    {
         tempZRot += 360;
    }
    // using local angles will keep proper rotations if we're parented
    refTransform.localEulerAngles.z = tempZRot;
}

By using the axis, you get 4 things:

  1. a way of easily maxing out your rotation

  2. have it reset without extra checks for key held/released

  3. automatically turn the proper direction; if you find directions are opposite from what you need, simply change +/- somewhere in that equation

  4. (bonus!) you get to play with the sensitivity / gravity values on the axis (see documentation on Input), to adjust how fast we reach this maximum bank degree or recover from it

We solved this problem in the past by converting both the upper and lower limit, as well as the current angle, into a defined "clamping space" (e.g., 0<=limitRight<=360 and limitLeft<=limitRight). Using this method, we can supply our limits as an arbitrary number (e.g., even way outside the 0..360 interval), and it will still be interpreted correctly. One of the main advantages is, that you can define any interval without twisting your brain thinking about wrap-around problems. For example, limitLeft=-10 and limitRight=10 maps to -10..10, but so does 350..10, or even -370..-350. On the other hand, it is also possible to map to the inverse interval: limitLeft=10 and limitRight=-10 maps to 10..350, and therefore allows only values >10 and <-10, and omits the range -10..10.

Furthermore, we added additional checks to a) correctly initialize the angle to the nearest border if it is outside the given interval, and b) if the current angle is set outside the allowed interval externally (e.g., by editing it directly in the Inspector), limit the interaction to one direction, the direction towards the nearest limit border (hard to explain, try it for yourself).

(Note that it can happen for large rotSpeed and the special case of "almost-full" 360 intervals (for example, 1 to 360) that the change in angle between two frames is too large and it "jumps" over the interval that is outside the allowed range (0 to 1))

using UnityEngine;

public class LimitedRotation : MonoBehaviour { 
    // limitLeft is the minimal allowed angle
    public float limitLeft = -10;

    // limitRight is the maximal allowed angle
    public float limitRight = 10;

    // rotSpeed is a general scaling factor
    public float rotSpeed = 200;

    private bool haveLimits;

    void Start(){
        float newAngle=0;

        // prepare rotation limit tests (left/right)
        if(limitLeft==limitRight){
            // unrestricted rotation
            haveLimits=false;
        }else{
            haveLimits=true;
            // make sure 0<=limitRight<=360 and limitRight-limitLeft=(0..360) (-360<=limitLeft<=360)
            while(limitRight<0)
                limitRight+=360;
            while(limitRight-limitLeft>=360)
                limitLeft+=360;
            while(limitLeft>limitRight)
                limitLeft-=360;

            if(limitLeft>0){
                // 0 is not in allowed interval; initialize the angle to the nearest valid boundary
                float rightLimit=Mathf.Min(limitRight,360-limitRight);
                float leftLimit=Mathf.Min(Mathf.Abs(limitLeft),360-Mathf.Abs(limitLeft));
                if(leftLimit>rightLimit)
                    newAngle = limitRight;
                else
                    newAngle = limitLeft;
            }
        }

        transform.localEulerAngles=new Vector3(0,0,newAngle);
    }

    void Update(){
        float deltaAngle=Input.GetAxis("Horizontal");
        float oldAngle = transform.localEulerAngles.z;
        float newAngle = oldAngle - rotSpeed * deltaAngle * Time.smoothDeltaTime;
        if(haveLimits){
            while(oldAngle>limitRight)
                oldAngle-=360;
            if(oldAngle>=limitLeft)
            {
                // clamp to limit
                while(newAngle>limitRight)
                    newAngle-=360;
                if(newAngle<limitLeft)
                    if(deltaAngle<0)
                        newAngle=limitRight; // rotate left limit
                    else
                        newAngle=limitLeft; // rotate right limit
            }else{
                // we are outside the limit
                // so only allow movement towards nearest limit
                float distToRight=Mathf.Abs(oldAngle-limitRight);
                distToRight=Mathf.Min(Mathf.Abs(oldAngle-limitRight-360),distToRight);
                distToRight=Mathf.Min(Mathf.Abs(oldAngle-limitRight+360),distToRight);
                float distToLeft=Mathf.Abs(oldAngle-limitLeft);
                distToLeft=Mathf.Min(Mathf.Abs(oldAngle-limitLeft-360),distToLeft);
                distToLeft=Mathf.Min(Mathf.Abs(oldAngle-limitLeft+360),distToLeft);
                if(deltaAngle<0 && distToLeft>distToRight ||
                   deltaAngle>0 && distToLeft<distToRight)
                    newAngle=oldAngle;
            }
        }
        while(newAngle<0) newAngle+=360;
        while(newAngle>360) newAngle-=360;

        transform.localEulerAngles=new Vector3(0,0,newAngle);
    } 
}

(yes, all "while()..." commands can be optimized by using an appropriate %/multiplication/fraction computation, instead of a stupid loop)

Note, in the code above limitLeft/limitRight can only be adjusted to a certain amount on-the-fly, since the initialization is done in Start(). If you want to adjust the limits freely (e.g., via script etc.), you should move the that initialization from Start() to an Update() function.