Waiting for time In my scriptable Object

I am trying to introduce a delay in my ScriptableObject (SO).
I can’t directly use MonoBehavior so I’m borrowing the coroutine from a singleton class called game_control

public void wait(float time)
{
    StartCoroutine(waitTime(time));
}

IEnumerator waitTime(float time)
{
    yield return new WaitForSeconds(time);
}

then in my SO class i have the wait function and use it like this

public void wait(float time) => game_control.control.wait(time);

public override void Activate(unit self, unit target)
{
   //----------------------hidden----------------------
    for (int j = 0; j < 3; j++)
    {
        wait(3);   
        target.receiveAttack(this, magnitude, target.data.defense);
    }
}

However; during runtime, the waiting part never comes up.
there’s no delay before or between the execution of target.receiveAttack()

What am I doing wrong?

You can not wait inside a normal method. Unity is single threaded (at least the scripting layer). So “waiting” in a normal method would mean your whole application would freeze. All you’re currently doing is starting a coroutine which runs in the background and does literally nothing. StartCoroutine immediately returns once the coroutine is started.

You have to put your code that needs to wait inside a coroutine. Coroutines do not need to be part of a MonoBehaviour. Coroutines just create an iterator object that needs to be passed to Unity’s coroutine scheduler. Yes, you need a MonoBehaviour to “host” your coroutine, but the coroutine itself can be anywhere.

    // in your scriptable object
    public override void Activate(unit self, unit target)
    {
        game_control.control.StartCoroutine(DoAction(self, target));
    }
    private IEnumerator DoAction(unit self, unit target)
    {
        for (int j = 0; j < 3; j++)
        {
            yield return new WaitForSeconds(3);
            target.receiveAttack(this, magnitude, target.data.defense);
        }
    }

Note that the coroutine will be hosted on that “control” MonoBehaviour instance. So if that gets deactivate or destroyed, the coroutine would be gone.

Thought I’d point out that, while Bunny83’s post is the most correct answer, you actually can wait inside a normal method via lambdas. It’s less performant, but very convenient. I haven’t used coroutines much for a few years, but I used to pull the following extension trick occasionally before I switched to other alternatives that abstract coroutines:

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

public static class CoroutineExtensions 
{
    private static List<IEnumerator> coroutineList;
    public static void Wait(this MonoBehaviour behaviour, Func<bool> condition, Action callback)
    {
        var newCoroutine = WaitUntilCoroutine(condition,callback);
        callback += () => coroutineList.Remove(newCoroutine);
        behaviour.StartCoroutine(newCoroutine);
    }

    private static IEnumerator WaitUntilCoroutine(Func<bool> condition, Action callback)
    {
        yield return new WaitUntil(condition);
        callback?.Invoke();
    }

    public static void WaitS(this MonoBehaviour behaviour, float seconds, Action callback)
    {
        var newCoroutine = WaitForSecondsCoroutine(seconds, callback);
        callback += () => coroutineList.Remove(newCoroutine);
        behaviour.StartCoroutine(newCoroutine);
    }

    private static IEnumerator WaitForSecondsCoroutine(float seconds, Action callback)
    {
        yield return new WaitForSeconds(seconds);
        callback?.Invoke();
    }
}

With CoroutineExtensions installed, your original code would work almost verbatim, with the following tweaks:

public void wait(float time, Action callback) => game_control.control.WaitS(time, callback);

public override void Activate(unit self, unit target)
{
   //----------------------hidden----------------------
    for (int j = 0; j < 3; j++)
    {
        wait(3,()=>{
	    	target.receiveAttack(this, magnitude, target.data.defense);
        });
    }
}

You wouldn’t have to write anything in game_control since it would use the extension instead.