Forum rules - please read before posting.

Syncing timer with custom animation

edited May 3 in Technical Q&A

I have a QuickTime event timer. I don't want to use the Slider to visualize it, but an animation of an antique stop watch with hands, that shows up on the screen (it is an animated graphic element in its own menu).

When the QuickTime event begins, a Run In Parallel action triggers both the time and the animation (the animation and timer are both 10s). The Timer is not set to run only during gameplay, and the animation action does not have a Wait until finish checked. Animation has Remember Animatior with Save change in controller and Retain Parameter values, so when I enter the main menu, the timer and the animation pause.

All runs smoothly at first, but the more I re-trigger the event and do other things with the player while the clock's ticking, the more the animation and the timer fall out of sync. The timer always works well. It is actually the animation that does not play fully.

Is there a way to make sure both start and end in sync, or is there a better way to animate a timer with custrom animation?

Comments

  • edited May 3

    I found the cause. It happens when I enter the Main menu and exit back to the game again. The animation reacts differently than the timer (animation is back on the same frame it was before entering the menu, but the timer moves to the next value), and that is when the disharmony happens.

    But I don't know what to do with it.

  • You're better off syncing the Animator to the normalised time of the QTE, which you can get with:

    AC.KickStarter.playerQTE.GetRemainingTimeFactor ();
    

    This can be placed in a script attached to the Animator to set the value of a float parameter named e.g. "Time":

    using UnityEngine;
    public class SyncQTETime : MonoBehaviour
    {
        void Update ()
        {
            float time = AC.KickStarter.playerQTE.GetRemainingTimeFactor ();
            GetComponent<Animator> ().SetFloat ("Time", time);
        }
    }
    

    This parameter can then be used to drive the playback of your animation.

  • AC.KickStarter.playerQTE.GetRemainingTimeFactor ();

    Thank you. I will also be using this in other events, not necessarily QT as described in the manual. They will depend on a custom timer created in the Timer Editor.

    How can I get the remaining time of such a custom timer?

  • Getting a Timer in script is done with:

    AC.KickStarter.variablesManager.GetTimer (2);
    

    Where "2" is replaced with the Timer's ID.

    The available properties can be found in the Scripting Guide.

  • I am having several issues implementing this.

    First, i am getting an error with the script: Cannot implicitly convert type 'AC.Timer' to 'float'

    using UnityEngine;
    public class SyncQTETime : MonoBehaviour
    {
        void Update()
        {
            float time = AC.KickStarter.variablesManager.GetTimer(2);
            GetComponent<Animator>().SetFloat("Time", time);
        }
    }
    

    I tried ChatGPT to help fix it:

    using UnityEngine;
    using AC;
    
    public class SyncQTETime : MonoBehaviour
    {
        void Update()
        {
            Timer timer = KickStarter.variablesManager.GetTimer(2);
            float time = timer.Value;
            GetComponent<Animator>().SetFloat("Time", time);
        }
    }
    

    I attached this script to the Image object containing the animator. But it does not work. I don't know if the problem is the script, me attaching it incorrectly, or both. The timer ID was actually 2 in the Timer Editor, so i left it like that :-)

  • In what way does it not work, exactly? The code looks fine.

    What are the properties of the Timer, and are you looking to use the absolute time value, or a normalised one (ie from 0 to 1)? If the latter, you can use timer.Progress instead of timer.Value.

  • It did not sync with the timer animation. In the end, I modified your script with the help of AI to this, and it seems to work. The timer is an absolute value, the movement of the clock in the animation is supposed to be smooth (not a step per second), and if there is a dissynchronization with the timer, then the hand just speeds up to the right position.

    using UnityEngine;
    using AC;
    
    public class SyncStopkySTimerem : MonoBehaviour
    {
        [Header("Animation Settings")]
        public string animationStateName = "Stopwatch";
        public float timerDuration = 30f;
    
        [Header("Sync Behavior")]
        public float maxDesyncSeconds = 1f;     // Start catch-up if this is exceeded
        public float normalSpeed = 1f;          // Normal playback rate
        public float catchUpSpeedMultiplier = 3f; // How much faster to catch up
    
        private Animator animator;
        private float currentNormalizedTime = 0f;
        private bool hasStarted = false;
    
        void Start()
        {
            animator = GetComponent<Animator>();
            if (animator == null)
            {
                Debug.LogError("Animator component not found on " + gameObject.name);
                enabled = false;
            }
    
            animator.speed = 0f; // We control animation manually
        }
    
        void Update()
        {
            Timer timer = KickStarter.variablesManager.GetTimer(2);
            if (timer == null || !timer.IsRunning)
            {
                hasStarted = false;
                animator.speed = 0f;
                return;
            }
    
            float targetNormalizedTime = Mathf.Clamp01(timer.Value / timerDuration);
    
            if (!hasStarted)
            {
                currentNormalizedTime = targetNormalizedTime;
                animator.Play(animationStateName, 0, currentNormalizedTime);
                hasStarted = true;
                return;
            }
    
            // Calculate desync in seconds
            float desyncSeconds = (targetNormalizedTime - currentNormalizedTime) * timerDuration;
    
            // Determine speed multiplier
            float deltaSpeed = normalSpeed;
            if (desyncSeconds > maxDesyncSeconds)
            {
                // We're behind, speed up
                deltaSpeed *= catchUpSpeedMultiplier;
            }
    
            // Advance current time forward only
            currentNormalizedTime += (Time.deltaTime / timerDuration) * deltaSpeed;
            currentNormalizedTime = Mathf.Min(currentNormalizedTime, targetNormalizedTime); // Never overshoot
    
            // Apply new position to animator
            animator.Play(animationStateName, 0, currentNormalizedTime);
        }
    }
    
Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Welcome to the official forum for Adventure Creator.