[Unity] Process at a constant tempo [Metronome]

4 minute read

I want to keep a constant tempo in Unity.

【environment】

· Mac OSX El Capitan
・ Unity versiton: 2018.3.0

If you want to play only the sound at a good tempo

If only the sound is okay, the mechanism of this article is very nice.
[Unity] Sound effects at accurate time intervals
When I was googled, I came across a Q / A that explains the code written in the above article, so I will post it.
[Unity] The tempo of the song is out of sync with the music game production

If you want to process other things at the same time as the sound

AudioSource.PlayScheduled (); makes a sound, but if you want to do another process at the same time as the sound, you need another method.

Case1: OK at the same tempo all the time! → Use InvokeRepeating ()

There is a way to use InvokeRepeating.
The advantage is that the code is simple and easy to read!
Enter the method name you want to repeat in the first argument, the waiting time (seconds) until it starts in the second argument, and the repeat interval (seconds) in the third argument.

public void InvokeRepeating (string methodName, float time, float repeatRate);
MonoBehaviour.InvokeRepeating

TempoMaker_InvokeRepeating.cs


//How to use invoke repeating
//https://docs.unity3d.com/ja/current/ScriptReference/MonoBehaviour.InvokeRepeating.html

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TempoMaker_InvokeRepeating : MonoBehaviour
{
    [SerializeField] AudioSource audioSource;
    public const float START_SECONDS = 0.0f;
    public const float INTERVAL_SECONDS = 1.0f;

    // Start is called before the first frame update
    void Start()
    {
        audioSource = GetComponent<AudioSource>();
        InvokeRepeating("PlaySound", START_SECONDS, INTERVAL_SECONDS);
    }

    private void PlaySound()
    {
        audioSource.Play();
        print("Played");
    }
}

Attach AudioSource and this script to an empty object.
Please put an appropriate AudioClip in AudioSource.
When you run the game, the sound will be played at regular intervals, and “Played” will increase on the console.

Case2: I want to change the tempo on the way! → Use coroutines

If you want to change the tempo to your liking on the way, use a coroutine.
You can change the tempo by changing the value of INTERVAL_SECONDS.
The content is almost the same as TempoMaker_InvokeRepeating.cs, but it adds the function to stop the tempo while pressing the mouse button.

C#TempoMaker_Coroutine.cs


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TempoMaker_Coroutine : MonoBehaviour
{
    [SerializeField] AudioSource audioSource;
    // Start is called before the first frame update
    private IEnumerator coroutine;
    public float INTERVAL_SECONDS = 1.0f;//Can be changed from the inspector (and of course from the script)
    void Start()
    {
        audioSource = GetComponent<AudioSource>();
        coroutine = TempoMake();
        StartCoroutine(coroutine);
        
    }

    // Update is called once per frame
    void Update()
    {
        //Stop while holding down the left mouse button, restart when released
        if (Input.GetMouseButtonDown(0)) {
            StopCoroutine(coroutine);
        }else if (Input.GetMouseButtonUp(0)){
            StartCoroutine(coroutine);
        }
    }

    IEnumerator TempoMake()//Plays a sound at regular intervals and displays "Played" on the console
    {
        while (true) {
            yield return
             new WaitForSecondsRealtime(INTERVAL_SECONDS);
            audioSource.Play();
            print("Played");
        }
    }
}

InvokeRepeating vs Coroutine

InvokeRepeating is highly readable, but it seems to be unsuitable for performance if you want to use it for a large number of objects.
https://answers.unity.com/questions/477862/what-is-the-best-between-startcoroutine-or-invoker.html

The advantages and disadvantages of Invoke Repating and coroutines are summarized in this article. (Translated)
InvokeRepeating vs Coroutines: Run a method at certain time intervals

Using an Invoke (or InvokeRepeating) is easier than using a coroutine. On the other hand, Coroutines are more flexible. You cannot pass a parameter to an invoked method but you can do this to a coroutine.

While Invoke (or InvokeRepeating) is easier to use than coroutines, coroutines are more flexible.
You can’t pass variables to Invoke, but coroutines can.

Another thing which we have to mention is coroutines are more performance-friendly than the Invoke. For basic games, it does not matter much but if you have several objects which do the same thing, you should consider using Coroutine instead of Invoke.

What’s more, coroutines perform better. It doesn’t really matter if it’s a simple game, but if you want to do the same thing with multiple objects, you should consider using coroutines instead of Invoke.

The last difference between Invoke and Coroutine which we will cover is the execution condition after the deactivation of the object. Invoke and InvokeRepeating do not stop after the game object is deactivated. On the other hand, this not true for coroutines. They stop after the game object is deactivated or destroyed. Therefore, you should use Invoke or InvokeRepeating, if you would like your method to continue running, even though the object is deactivated after the method is triggered.

The last difference is the execution status after deactivating the object. Invoke and InvokeRepeating do not stop after deactivating a GameObject. Coroutines, on the other hand, stop when you deactivate or destroy a GameObject. So if you want to keep the method running even if the object becomes inactive, use Invoke or InvokeRepeating.

that’s all. Coroutines are convenient.