Programming Accurate Clock Hands

So, I recently followed a tutorial on scripting the simple hands of a clock.
However, I want to build further on the program by making the hands point accurately relative to the other hands, just like a real-life analog clock.

Here’s the current code I have for the Clock Animator:

public class ClockAnimator : MonoBehaviour {

    private const float
       hoursToDegrees = 360f / 12f, 
       minutesToDegrees = 360f / 60f, 
       secondsToDegrees = 360f / 60f;

    public Transform hours, minutes, seconds;
    	
	// Update is called once per frame
	void Update () {
        DateTime time = DateTime.Now;
        hours.localRotation = Quaternion.Euler(0f, 0f, time.Hour * -hoursToDegrees);
        minutes.localRotation = Quaternion.Euler(0f, 0f, time.Minute * -minutesToDegrees);
        seconds.localRotation = Quaternion.Euler(0f, 0f, time.Second * -secondsToDegrees);
    }

What should I do to start on this?

Thanks!

**EDIT: ** Thanks for everyone who helped with the solution!

Here’s what I got:

public class ClockAnimator : MonoBehaviour
{
    private const float
        hoursToDegrees = 360f / 12f,
        minutesToDegrees = 360f / 60f,
        secondsToDegrees = 360f / 60f;

    public float rHours, rMinutes, rSeconds = 0;
    int iHours, iMinutes, iSeconds;


    public Transform hours, minutes, seconds;
    public bool analog = true; // Smooth == true, Rigid TickTock == false.

    [Header("Custom Time")]
    public bool useCustomTime = true;
    public bool activeTime = true;

    public float m_timeAmplifier = 20f;
    public DayCycle m_customTime = new DayCycle();
    [Serializable]
    public class DayCycle
    {
        [SerializeField]
        public float m_time;

        private float m_hours;
        private float m_minutes;
        private float m_seconds;


        public float Hours { get { return Mathf.Repeat(m_time / 3600f, 24f); } }
        public float Minutes { get { return Mathf.Repeat(m_time / 60f, 60f); } }
        public float Seconds { get { return Mathf.Repeat(m_time, 60f); } }
        public bool militaryTime = false;

        public void AddSeconds(float durationInSeconds)
        {
            m_time += durationInSeconds;
            if (m_time > 86400) // == 60*60*24 wrap around after a day.
                m_time -= 86400;
            //if (m_time > 43200) // 
            //    m_time -= 43200;
        }

        public void AddMinutes(float durationInMinutes) { AddSeconds(durationInMinutes * 60f); }
        public void AddHours(float durationInHours) { AddSeconds(durationInHours * 3600f); }

        public void SetTime(int aHour, int aMinute, int aSecond)
        {
            //if (!militaryTime)
            //{
            
            //    if (aHour > 12)
            //    {
            //        aHour -= 12;
            //    }
            //    if (aHour < 1)
            //    {
            //        aHour = 12;
            //    }
            //}
            m_time = (aHour * 60f + aMinute) * 60f + aSecond;
            Debug.Log("Set Time = (" + aHour + "* 60 " + "+ " + aMinute + ")" + " * 60 + " + aSecond + " = " + m_time);
        }

        public void SetTime(float hours, float minutes, float seconds)
        {
            m_hours = 0.0f;
            m_minutes = 0.0f;
            m_seconds = 0.0f;

            AddHours(hours);
            AddMinutes(minutes);
            AddSeconds(seconds);
        }

        


        /*[SerializeField]
        private float m_hours = 7, m_minutes = 5, m_seconds = 23;
        public float Hours { get { return m_hours; } set { m_hours = Mathf.Repeat( value, 24.0f );  } }
        public float Minutes { get { return m_minutes; } set { m_minutes = Mathf.Repeat( value, 60.0f ); } }
        public float Seconds { get { return m_seconds; } set { m_seconds = Mathf.Repeat( value, 60.0f ); } }
        



        private const float m_hourInSeconds = 3600.0f,
                            m_minuteInSeconds = 60.0f;

        

        public void AddMinutes(float durationInMinutes) { AddSeconds( durationInMinutes * m_minuteInSeconds ); }
        public void AddHours( float durationInHours ) { AddSeconds( durationInHours * m_hourInSeconds ); }
        public void RandomizeTime()
        {
            AddSeconds(UnityEngine.Random.Range(0f, 60f));
            AddMinutes(UnityEngine.Random.Range(0f, 60f));
            AddHours(UnityEngine.Random.Range(0f, 12f));

        }*/
    }

    void Start()
    {
        if(!activeTime)
        {
            //m_customTime.RandomizeTime();
            rSeconds = UnityEngine.Random.Range(0, 60);
            rMinutes = UnityEngine.Random.Range(0, 60);
            rHours = UnityEngine.Random.Range(0, 12);
            //m_customTime.SetTime(UnityEngine.Random.Range(0, 60), UnityEngine.Random.Range(0, 60), UnityEngine.Random.Range(0, 12));
            m_customTime.SetTime(rSeconds, rMinutes, rHours);

        }
    }

    void RandomizeTime()
    {
        rSeconds = UnityEngine.Random.Range(0, 60);
        rMinutes = UnityEngine.Random.Range(0, 60);
        rHours = UnityEngine.Random.Range(0, 12);
        //m_customTime.SetTime(UnityEngine.Random.Range(0, 60), UnityEngine.Random.Range(0, 60), UnityEngine.Random.Range(0, 12));
        m_customTime.SetTime(rSeconds, rMinutes, rHours);
    }

    void DisplayTime(float _seconds, float _minutes, float _hours)
    {
        if (m_customTime.militaryTime) { Debug.Log(ToMilitaryString(_seconds, _minutes, _hours)); }
        else { Debug.Log("Display Time: " + ToStandardTime(_seconds, _minutes, _hours)); }
    }

    void DisplayTime()
    {
        if (m_customTime.militaryTime) { Debug.Log(ToMilitaryString(rSeconds, rMinutes, rHours)); }
        else { Debug.Log("Display Time: " + ToStandardTime(rSeconds, rMinutes, rHours)); }
    }

    void Update()
    {
        if (Input.GetKey(KeyCode.R))
        {
            //m_customTime.SetTime(UnityEngine.Random.Range(0, 60), UnityEngine.Random.Range(0, 60), UnityEngine.Random.Range(0, 24));
            rSeconds = UnityEngine.Random.Range(0, 60);
            rMinutes = UnityEngine.Random.Range(0, 60);
            rHours = UnityEngine.Random.Range(0, 12);
            //m_customTime.SetTime(UnityEngine.Random.Range(0, 60), UnityEngine.Random.Range(0, 60), UnityEngine.Random.Range(0, 12));
            m_customTime.SetTime(rSeconds, rMinutes, rHours);

        }
        if (useCustomTime)
        {
            if (activeTime)
                m_customTime.AddSeconds(Time.deltaTime * m_timeAmplifier);

            int h = Mathf.FloorToInt(m_customTime.Hours);
            int m = Mathf.FloorToInt(m_customTime.Minutes);
            int s = Mathf.FloorToInt(m_customTime.Seconds);
            Debug.Log("Line163");
            Debug.Log("Time is " + h.ToString("00") + ":" + m.ToString("00") + ":" + s.ToString("00"));

            //Debug.LogFormat("{0};{1};{2}", m_customTime.Hours.ToString("00"), m_customTime.Minutes.ToString("00"), m_customTime.Seconds.ToString("00"));
            //Debug.Log("Line 165: Time = (" + m_customTime.Hours + "h * 60 " + "+ " + m_customTime.Minutes + "m)" + " * 60 + " + m_customTime.Seconds + "s = " + m_customTime.m_time);

            if (analog)
            {
                hours.localRotation =
                    Quaternion.Euler(0f, 0f, m_customTime.Hours * -hoursToDegrees);
                minutes.localRotation =
                    Quaternion.Euler(0f, 0f, m_customTime.Minutes * -minutesToDegrees);
                seconds.localRotation =
                    Quaternion.Euler(0f, 0f, m_customTime.Seconds * -secondsToDegrees);

                //hours.localRotation =
                //    Quaternion.Euler(0f, 0f, m_customTime.Hours * -hoursToDegrees);
                //minutes.localRotation =
                //    Quaternion.Euler(0f, 0f, m_customTime.Minutes * -minutesToDegrees);
                //seconds.localRotation =
                //    Quaternion.Euler(0f, 0f, m_customTime.Seconds * -secondsToDegrees);

            }
            else
            {
                hours.localRotation =
                    Quaternion.Euler(0f, 0f, Mathf.RoundToInt(m_customTime.Hours) * -hoursToDegrees);
                minutes.localRotation =
                    Quaternion.Euler(0f, 0f, Mathf.RoundToInt(m_customTime.Minutes) * -minutesToDegrees);
                seconds.localRotation =
                    Quaternion.Euler(0f, 0f, Mathf.RoundToInt(m_customTime.Seconds) * -secondsToDegrees);
            }

            if (m_customTime.militaryTime) { Debug.Log(ToMilitaryString()); }
            else { Debug.Log(ToStandardTime()); }
        }
        else
        {
            if (analog)
            {
                TimeSpan timespan = DateTime.Now.TimeOfDay;
                hours.localRotation =
                    Quaternion.Euler(0f, 0f, (float)timespan.TotalHours * -hoursToDegrees);
                minutes.localRotation =
                    Quaternion.Euler(0f, 0f, (float)timespan.TotalMinutes * -minutesToDegrees);
                seconds.localRotation =
                    Quaternion.Euler(0f, 0f, (float)timespan.TotalSeconds * -secondsToDegrees);
            }
            else
            {
                DateTime time = DateTime.Now;
                hours.localRotation = Quaternion.Euler(0f, 0f, time.Hour * -hoursToDegrees);
                minutes.localRotation = Quaternion.Euler(0f, 0f, time.Minute * -minutesToDegrees);
                seconds.localRotation = Quaternion.Euler(0f, 0f, time.Second * -secondsToDegrees);
            }

            DateTime currentTime = DateTime.Now;
            DisplayTime();
            //Debug.LogFormat("{0};{1};{2}", currentTime.Hour.ToString("00"), currentTime.Minute.ToString("00"), currentTime.Second.ToString("00"));

        }
    }

    public string ToMilitaryString() // Otherwise known as digital representation I suppose.
    {
        //return string.Format("{0};{1};{2}", Hours.ToString("00"), Minutes.ToString("00"), Seconds.ToString("00"));
        return string.Format("{0};{1};{2}", m_customTime.Hours.ToString("00"), m_customTime.Minutes.ToString("00"), m_customTime.Seconds.ToString("00"));
    }

    public string ToMilitaryString(float _seconds, float _minutes, float _hours) // Otherwise known as digital representation I suppose.
    {
        //return string.Format("{0};{1};{2}", Hours.ToString("00"), Minutes.ToString("00"), Seconds.ToString("00"));
        return string.Format("{0};{1};{2}", m_customTime.Hours.ToString("00"), m_customTime.Minutes.ToString("00"), m_customTime.Seconds.ToString("00"));
    }


    public string ToStandardTime()
    {
        float clampedHours = m_customTime.Hours % 12; // Get rest time of 0 to 12, AM or PM.
        string partOfDay = m_customTime.Hours > 12 ? "PM" : "AM";

        // Should result in "10:34 AM" or "07:48 PM".
        // Optionally you can get rid of using clamped hours if you just want to have the AM / PM postfix.
        return string.Format("{0};{1};{2}", clampedHours.ToString("00"), m_customTime.Minutes.ToString("00"), m_customTime.Seconds.ToString("00")) + " " + partOfDay;
    }

    public string ToStandardTime(float _seconds, float _minutes, float _hours)
    {
        float clampedHours = m_customTime.Hours % 12; // Get rest time of 0 to 12, AM or PM.
        string partOfDay = m_customTime.Hours > 12 ? "PM" : "AM";

        // Should result in "10:34 AM" or "07:48 PM".
        // Optionally you can get rid of using clamped hours if you just want to have the AM / PM postfix.
        return string.Format("{0};{1};{2}", clampedHours.ToString("00"), m_customTime.Minutes.ToString("00"), m_customTime.Seconds.ToString("00")) + " " + partOfDay;
    }
}

Hey @Czar-Man, I upgraded the package for you!
This was actually quite a bit tougher than I thought!

Preview: Screen capture - 3cbbd5e7615946210b4a4e37fc31ade2 - Gyazo

Updated Package:

Changelist:

  • Now able to set custom time.

  • Now able to set seconds, minutes and hours individually.

  • Now able to increment custom time with X seconds, minutes and hours.

  • Now able to set time amplifier that speeds up custom time! (also works with negative values)

  • Now debugs current time, remove it if you wish.

  • Now takes looping into consideration when incrementing / setting time.

Original tutorial as from OP. (for other users)

using UnityEngine;
using System;

public class ClockAnimator : MonoBehaviour
{
    private const float
        hoursToDegrees = 360f / 12f,
        minutesToDegrees = 360f / 60f,
        secondsToDegrees = 360f / 60f;

    public Transform hours, minutes, seconds;
    public bool analog = true; // Smooth == true, Rigid TickTock == false.

    [Header("Custom Time")]
    public bool useCustomTime = true;
    public float m_timeAmplifier = 20f;
    public DayCycle m_customTime = new DayCycle();

    [Serializable]
    public class DayCycle
    {
        [SerializeField]
        private float m_hours = 7, m_minutes = 5, m_seconds = 23;

        public float Hours { get { return m_hours; } set { m_hours = Mathf.Repeat( value, 24.0f );  } }
        public float Minutes { get { return m_minutes; } set { m_minutes = Mathf.Repeat( value, 60.0f ); } }
        public float Seconds { get { return m_seconds; } set { m_seconds = Mathf.Repeat( value, 60.0f ); } }

        private const float m_hourInSeconds = 3600.0f,
                            m_minuteInSeconds = 60.0f;

        public void AddSeconds(float durationInSeconds)
        {
            Hours += durationInSeconds / m_hourInSeconds;
            Minutes += durationInSeconds / m_minuteInSeconds;
            Seconds += durationInSeconds;
        }

        public void AddMinutes(float durationInMinutes) { AddSeconds( durationInMinutes * m_minuteInSeconds ); }
        public void AddHours( float durationInHours ) { AddSeconds( durationInHours * m_hourInSeconds ); }
    }

    void Update()
    {
        if( useCustomTime )
        {
            m_customTime.AddSeconds( Time.deltaTime * m_timeAmplifier );
            Debug.LogFormat( "{0};{1};{2}", m_customTime.Hours.ToString( "00" ), m_customTime.Minutes.ToString( "00" ), m_customTime.Seconds.ToString( "00" ) );

            if( analog )
            {
                hours.localRotation =
                    Quaternion.Euler( 0f, 0f, m_customTime.Hours * -hoursToDegrees );
                minutes.localRotation =
                    Quaternion.Euler( 0f, 0f, m_customTime.Minutes * -minutesToDegrees );
                seconds.localRotation =
                    Quaternion.Euler( 0f, 0f, m_customTime.Seconds * -secondsToDegrees );
            }
            else
            {
                hours.localRotation =
                    Quaternion.Euler( 0f, 0f, Mathf.RoundToInt( m_customTime.Hours ) * -hoursToDegrees );
                minutes.localRotation =
                    Quaternion.Euler( 0f, 0f, Mathf.RoundToInt( m_customTime.Minutes ) * -minutesToDegrees );
                seconds.localRotation =
                    Quaternion.Euler( 0f, 0f, Mathf.RoundToInt( m_customTime.Seconds ) * -secondsToDegrees );
            }
        }
        else 
        {
            if( analog )
            {
                TimeSpan timespan = DateTime.Now.TimeOfDay;
                hours.localRotation =
                    Quaternion.Euler( 0f, 0f, ( float )timespan.TotalHours * -hoursToDegrees );
                minutes.localRotation =
                    Quaternion.Euler( 0f, 0f, ( float )timespan.TotalMinutes * -minutesToDegrees );
                seconds.localRotation =
                    Quaternion.Euler( 0f, 0f, ( float )timespan.TotalSeconds * -secondsToDegrees );
            }
            else
            {
                DateTime time = DateTime.Now;
                hours.localRotation = Quaternion.Euler( 0f, 0f, time.Hour * -hoursToDegrees );
                minutes.localRotation = Quaternion.Euler( 0f, 0f, time.Minute * -minutesToDegrees );
                seconds.localRotation = Quaternion.Euler( 0f, 0f, time.Second * -secondsToDegrees );
            }

            DateTime currentTime = DateTime.Now;
            Debug.LogFormat( "{0};{1};{2}", currentTime.Hour.ToString( "00" ), currentTime.Minute.ToString( "00" ), currentTime.Second.ToString( "00" ) );
        }
    }
}

To set the time, just use:

        // For Time Setting.
        m_customTime.Minutes = 30f;

        // For Time Adding.
        m_customTime.AddSeconds( 40f );

I hope that’s what you’re looking for. If it is, please accept this answer, this time forever! And have a nice day! :slight_smile: