How can I use Coroutines in ScriptableObject?

I can only use coroutines in MonoBehaviours. How can I workaround in order to get a similar behaviour in ScriptableObjects?

I can’t see a registered user answer here (I will not mention his pseudonym as I am thinking it was intentional to delete it), but I received a mail alert of his sumbission which contained enough information to resolve my query.

The solution was to create another class which extends MonoBehaviour and then call StartCoroutine from this class. I don’t use exactly what the user described in his answer but it gives the general idea:

Declare the coroutine using another MonoBehaviour. For example:

public class Example : MonoBehaviour {
    public static Example instance;
    
    void Start() {
        Example.instance = this;
    }
}

public class Scriptable : ScriptableObject {
    public void doStuff() {
        Example.instance.StartCoroutine(myCoroutine());
    }
}

Thanks mister!

In my case, I wanted to keep the coroutine in a simple class (not inheriting anything), but call the function which triggers the couroutine from a MonoBehaviour.

So what I did was pass the MonoBehaviour to the function that I wanted to start the coroutine from.

Here was my class that has the couroutine (Note that I don’t put the name of the coroutine in quotes when calling StartCoroutine()):

public class VolumeChangeIndicator 
{
	private float timer, volume;
	private float delay = 0.1F;
	private bool running;
	private AudioClip soundClip;
	private AudioSource audioSource;
	public VolumeChangeSound(AudioSource audioSource, AudioClip soundClip){
		this.audioSource = audioSource;
		this.soundClip = soundClip;
	}
    // This function triggers the coroutine.
	public void VolumeSet(MonoBehaviour caller, float value){	
		volume = value;
		timer = delay;
		if (!running){
			caller.StartCoroutine(VolChangeSound());
		}
	}
	IEnumerator VolChangeSound(){
		running = true;
		while (timer > 0){
			timer -= Time.unscaledDeltaTime;
			yield return new WaitForEndOfFrame();
		}
		timer = 0;
		audioSource.PlayOneShot(soundClip, volume);
		running = false;
	}
}

Then in my MonoBehavior, I did this:

public AudioClip volumeIndicatorSound;
private AudioSource audioSource;
private VolumeChangeIndicator volumeChangeIndicator;

void Start(){
	audioSource = gameObject.AddComponent<AudioSource>();
     // Instantiate the class...
	volumeChangeIndicator = new VolumeChangeIndicator(audioSource, volumeIndicatorSound);
}

// This function is called by a slider every time the value is changed.
public void SetVolume(float value){
    SetVolume(value, true);
}

public void SetVolume(float value, bool playSound){
	PlayerPrefs.SetFloat("master_volume",value);
	if (playSound){
        // Pass this MonoBehaviour to the function so it can be used to run the coroutine.
		volumeChangeIndicator.VolumeSet(this, value);
	}
}

I made a small forum thread about this which might be interesting.
Its an alternative to the stuff I saw here and its an awesome in my opinion.

https://forum.unity.com/threads/scriptableobjects-and-coroutines.1112239/

@Dakwamine You use ScriptableObject when you have creating asset on mind, so you can move across scenes and projects or load run-time. Binding to Scene GameObject’s MonoBehaviour you are strangled to that Scene or scenes if you DontDestroy GameObject.

public class Scriptable : ScriptableObject {
   GameObject PrefabGameObjectWithExampleMonoBehaviour;
     public void doStuff() {
        PrefabGameObjectWithExampleMonoBehaviour.GetComponent<Example>().StartCoroutine(myCoroutine());
     }

@Bunny83 You not need to make instance of the PrefabGameObjectWithExampleMonoBehaviour by including Scriptable which is referencing PrefabGameObjectWithExampleMonoBehaviour, Unity creates hidden instance which when there is no other instance can be consider as singleton.
}

You can now move .asset and prefab as pack and you wont lose functionality.

Edit: Code is pseudo!

I thought you did like OnEnable() inside of the scriptable object…