Search code examples
c#unity-game-enginequaternions

Unity C# Pendulum withRotation


I am a unity newbie, can I get some help?

So basically I created a pendulum, but i added some function where, if it reached the maximum height possible, the Y axis of the pendulum will rotate by 180 degrees but the Y axis wasn't rotating.

Here's the code:

using UnityEngine;

public class PendulumScript : MonoBehaviour
{
    [SerializeField] public float speed = 1.5f;
    public float limit = 75f;
    public bool randomStart = false;
    private float random = 0;
    private bool limitReached = false;
    private Quaternion originalRotation;

    void Awake()
    {
        if (randomStart)
        {
            random = Random.Range(0f, 1f);
        }

        // Save the original rotation
        originalRotation = transform.localRotation;
    }

    void Update()
    {
        float angle = limit * Mathf.Sin(Time.time + random * speed);
        transform.localRotation = Quaternion.Euler(0, 0, angle);

        if (!limitReached && (angle == limit))
        {
            // Rotate around the Y-axis by 180 degrees
            transform.Rotate(0, 180 * Time.time, 0);
            limitReached = true;
            limit *= -1; // turns limit into negative
        }
        else if (limitReached && (angle == -limit))
        {
            // Reset to original rotation which is Y = 0
            transform.localRotation = originalRotation;
            limitReached = false;
            limit *= -1; // turns limit into positive
        }
    }
}

i was trying the rotation using if !limitreached and else if limitreached and it does rotate but flickering since its doing the rotation all the time, thats why I added another condition, but it appears the best condition that i could think of is not working, im hoping to figure out on whats wrong with the (angle == limit) condition that it's not being satisfied or maybe what really is the reason why its not working.


Solution

  • So if I got that right what you want is actually two rotations

    • The pendulum rotates continuously forth and back on the Z axis
    • Additionally once reaching the limit on either side you additionally want the 180° rotation on Y

    There are various issues with your approach

    • This

      transform.localRotation = Quaternion.Euler(0, 0, angle);
      

      Does NOT only rotate on the Z axis! It rather hard resets y = 0.

    • Never compare float using ==!

      Due to precision limits this might never ever be true

    • Your condition is also only true during max one single frame.

      You rather want to trigger a continuous rotation around Y

    • I also think that

      Time.time + random * speed
      

      is not quite what you wanted to use but rather e.g.

      (Time.time + random) * speed
      
    • And

      transform.Rotate(0, 180 * Time.time, 0);
      

      Does NOT rotate by 180° ... the more time has passed the more it will rotate here!

    I would actually treat this as two individual rotations and handle them separately:

    public class PendulumScript : MonoBehaviour
    {
        public float speed = 1.5f;
        public float limit = 75f;
        public bool randomStart = false;
        public float yRotationSpeed = 45.0f; // degrees per second
    
        private float rotationZ;
        private float rotationY;
        private bool performingYFlip;
        private float time;
        private float targetYRotation;
    
        void Awake()
        {
            // instead of selecting a random sin value
            // I would rather select a random start time
            // This way you have a clear check when enough time has passed to 
            // perform the flip
            if(randomStart)
            {
                // sinus range is in radians => full cycle every 2π
                time = Random.Range(0f, 2 * Mathf.PI) * speed;
    
                // if required could also adjust the initial targetYRotation based on the random time here
            }
        }
    
        void Update()
        {
            // simply increase the timer by the time passed since last frame
            time += Time.deltaTime * speed;
    
            // calculate Z rotation as before
            rotationZ = Mathf.Sin(time) * limit;
    
            // Use approximate equality for angles
            // note that due to the sinus even this might never be true within a frame
            //if(Mathf.Approximately(rotationZ, limit))
            // so you might even use a wider range like e.g.
            if(!performingYFlip  && Mathf.Abs(rotationZ - limit) <= /*Some Threshold in angles*/ 5f)
            {
                // initiate a flip
                performingYFlip = true;
                targetYRotation += 180f;
            }
    
            if(performingYFlip)
            {
                // continuous Y rotation as long as performingYFlip remains true
                yRotation = Mathf.MoveTowards(yRotation, targetYRotation, yRotationSpeed * Time.deltaTime);
    
                if(Mathf.Approximately(yRotation, targetYRotation))
                {
                    performingYFlip = false;
                }
            }
    
            // and finally apply both rotations together
            transform.rotation = Quaternion.Euler(0, yRotation, zRotation);
        }
    }