Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length

 
Advanced search

1324813 Posts in 59740 Topics- by 50960 Members - Latest Member: marzenadobraw

December 12, 2017, 09:57:43 am

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)When to use coroutines?
Pages: [1]
Print
Author Topic: When to use coroutines?  (Read 892 times)
PogueSquadron
Level 1
*



View Profile WWW
« on: May 27, 2017, 05:41:27 pm »

I'm still new to this and in working on my prototype, I think I've been using coroutines improperly.

When is it a good idea to use coroutines exactly? The course I took used respawning as an example so that it would be easy to add a delay to the respawn using WaitForSeconds. This had me thinking that coroutines could have all sorts of uses. When is it a bad idea though? For instance, I wanted to use one to implement a shoulder charge in my character, using WaitForSeconds as a way to measure the duration of his "dash." Am I overthinking it? Should I just use a counter that uses -= Time.deltaTime instead?

Or am I correct in that coroutines can be used fairly often without worry? Is there a general rule of thumb in regards to what they should be used for?
Logged
Teknokommari
Level 0
**


View Profile WWW
« Reply #1 on: May 28, 2017, 03:07:55 am »

I'm still new to this and in working on my prototype, I think I've been using coroutines improperly.

When is it a good idea to use coroutines exactly? The course I took used respawning as an example so that it would be easy to add a delay to the respawn using WaitForSeconds. This had me thinking that coroutines could have all sorts of uses. When is it a bad idea though? For instance, I wanted to use one to implement a shoulder charge in my character, using WaitForSeconds as a way to measure the duration of his "dash." Am I overthinking it? Should I just use a counter that uses -= Time.deltaTime instead?

Or am I correct in that coroutines can be used fairly often without worry? Is there a general rule of thumb in regards to what they should be used for?

Writing your own Timer class for Unity is so simple and you'll get a lot more control that way than using co-routines. You can easily add stuff like pause, reset and start for timers and handling multiple timers will be a lot easier and cleaner.

Problem with co-routines is that they can cause problems if the GameObject is disabled for some reason. Also you might want to pause the timer for some reason which can be messy with co-routines.


Code:

//Timer.cs
public class Timer
{
    public float Duration { get; private set; }
    public float Remaining { get; private set; }
    public bool Running { get; private set; }
    
    public void Start(float time)
    {
        Duration = time;
        Remaining = time;
        Running = true;
    }
    
    public void Reset()
    {
        Remaining = 0.0f;
        Duration = 0.0f;
        Running = false;
    }

    public void Restart()
    {
        Remaining = Duration;
    }

    public bool Tick(float dt)
    {
        if (!Running)
            return false;

        if (0.0f > Remaining)
        {
            return true;
        }
        else
        {
            Remaining -= dt;
            return false;
        }
    }
}

//Test.cs
public class Test : MonoBehaviour
{    
    Timer exampleTimer = new Timer();

    void Start ()
    {
        exampleTimer.Start(1.0f);
    }

    void Update ()
    {
if(exampleTimer.Tick(Time.deltaTime))
        {
            exampleTimer.Restart();
            Debug.Log("Tick");
        }
    }
}

« Last Edit: May 28, 2017, 11:11:14 am by Teknokommari » Logged

NoLocality
Level 1
*


AssetsAssetsAssetsAssetsAssets...


View Profile
« Reply #2 on: May 28, 2017, 09:46:51 am »

I second the "make you own timer class" as an alternative for co-routines that would go on for extended periods.  Co-routines can be resource hungry on the processor I hear, tbh I stress tested this myself in Unity and it doesn't seem too bad.


When is it a good idea to use coroutines exactly?

I'd say for anything that doesn't last long.

The last co-routine I used was a .01 second delay for a bool to switch so I could use the same button to mount/unmount a rideable dinosaur without the player immediately hopping right back off the second they hop on.


Co-routines have their places, just don't make a thousand simultaneous ones and you should be good.
Logged

Teknokommari
Level 0
**


View Profile WWW
« Reply #3 on: May 28, 2017, 11:18:25 am »

The last co-routine I used was a .01 second delay for a bool to switch so I could use the same button to mount/unmount a rideable dinosaur without the player immediately hopping right back off the second they hop on.

Are you perhaps using Input.GetButton instead of Input.GetButtonDown?
The latter only fires once on button press while other one does so while the button is held down.

On mobile devices buttons and IPointerClickHandler should be quite the same.
Logged

NoLocality
Level 1
*


AssetsAssetsAssetsAssetsAssets...


View Profile
« Reply #4 on: May 28, 2017, 12:15:24 pm »

Naw the inputs were good, it just seemed to satisfy both conditions at the same time with the same button press.

Not only would it despawn the player and begin the riding behaviors but it would respawn the player and shut the behaviors down at the same time.  The curious thing is this would only happen on the first attempt per scene then it would function correctly every time thereafter.

Tossed in that fraction of a second bool delay to assure the second condition wasn't met on the first button press and never saw a problem again.  As with most bugs it took some poking around to find a proper solution, tinkered with lateupdate, made sure I wasn't OnButton-ing, probably tried some other band-aid solutions, probably learned a thing or two...this was awhile ago lol.

Logged

DrHogan
Level 0
**



View Profile WWW
« Reply #5 on: May 29, 2017, 10:48:53 pm »

I think using coroutines for what you're doing works, but it depends a lot on implementation (if I understood correctly the "problem"). If the spawn is part of the coroutine itself, and the spawn is heavy (if it is, consider pooling anyway in that case to speed it up), then coroutines are perfect, not just for timing, but to avoid having your updates clogged and the fps dropping. Me personally I time the spawning with a simple usage of deltaTime, but then I call the spawn function as a coroutine (it is lightweight, but i like to stay on the safe side of it).

The real beauty of coroutines is that they run "outside" the updates, and therefore they don't impact performances. Consider some risks they have, i.e. you will have to handle the sync with the update manually if that is needed, and also as already mentioned, consider the risk of having them hanging across time for too long.
Logged

Dr.Hogan-Lead Developer at H&R Games
------------------------------------------------------
Empires in ruins
NoLocality
Level 1
*


AssetsAssetsAssetsAssetsAssets...


View Profile
« Reply #6 on: May 30, 2017, 11:25:25 am »

The mentioned code for everyone's perusal, silly comments, naming conventions, debug logs and all.


Code:
using UnityEngine;
using System.Collections;

public class RideTheDerp : MonoBehaviour {

public Behaviour[] RidingBehaviours;
public Behaviour[] IdleBehaviours;

private bool activenow = false;
public Camera DerpCam;

public Transform dismountLocation;
public Transform riderLocation;
public GameObject riderPrefab;

private GameObject player;

// Use this for initialization
void Start () {

//CHANGE THIS LATER!!!...this may not work for instantiated Derplidocus's...if they are
//instatiated when there is no "Player" tag object will lead to BAD THINGS!
//player = GameObject.FindGameObjectWithTag ("Player");

foreach(Behaviour vb in RidingBehaviours){
vb.enabled = false;
}
DerpCam.enabled = false;

activenow = false;
}

// Update is called once per frame
void Update () {
if (Input.GetButtonDown("E") && activenow) {
//if (activenow){
//activenow = false;

foreach(Behaviour vb in RidingBehaviours){
vb.enabled = false;
}

foreach(Behaviour vb in IdleBehaviours){
vb.enabled = true;
}
//GameObject Rider = riderLocation.transform.GetChild(0);

//Destroy(Rider);

foreach (Transform child in riderLocation) {
GameObject.Destroy(child.gameObject);
}

DerpCam.tag = "Untagged";
DerpCam.enabled = false;
player.transform.position = dismountLocation.transform.position;
player.transform.rotation = dismountLocation.transform.rotation;
player.SetActive(true);
activenow = false;


}

}

void Interact(){
Debug.Log ("Honk");
//player = GameObject.FindGameObjectWithTag ("Player");
player = GameObject.Find("Player");

if (activenow == false) {
//activenow = true;

foreach(Behaviour vb in RidingBehaviours){
vb.enabled = true;
}

foreach(Behaviour vb in IdleBehaviours){
vb.enabled = false;
}

GameObject Rider = (GameObject)Instantiate(riderPrefab, riderLocation.transform.position, riderLocation.transform.rotation);
Rider.transform.parent = riderLocation;

DerpCam.tag = "MainCamera";
DerpCam.enabled = true;
player.SetActive(false);
//activenow = true;
StartCoroutine(delayedPositive());
}
}

IEnumerator delayedPositive(){
yield return new WaitForSeconds (.1f);
activenow = true;
}
}

Now that I'm looking at it after a few months I'm thinking that the player was hopping off because I put the dismount code in the update and not in it's own method tbh.

But yeah that's how I last used coroutines...the coroutine method is at the bottom of the class and it's implemented at the tail-end of the Interact method.  If you don't call upon the 'delayedPositive' and uncomment out the 'activenow = True;' in the interact method you can experience the quirk first-hand.

Granted it looks like a band-aid fix to me now that I can think of a better way to do this (without using coroutines at all), but laughing at old code's inefficiencies/silliness is a good sign I hear haha.


Hmmm...now I'm kinda at a loss for good places to use coroutines aside from...
The real beauty of coroutines is that they run "outside" the updates, and therefore they don't impact performances. Consider some risks they have, i.e. you will have to handle the sync with the update manually if that is needed, and also as already mentioned, consider the risk of having them hanging across time for too long.
That makes a lot of sense and is probably gonna come in handy to know.

 Coffee
Logged

DrHogan
Level 0
**



View Profile WWW
« Reply #7 on: May 30, 2017, 11:18:21 pm »

Of course when I say "it has no impact" on performances, it means only in terms of the frame loop update (a crazy heavy coroutine might still clog the CPU). Examples of things I use the coroutines for

Pathfinding algorithms for units (you just make sure that the unit starts moving once the coroutine returned)
Some UI stuff (Unity UIs can be pretty heavy, so for example for scroll lists, i render some panels to texture before adding them to the panel, and that is co-routine based)
Spawning units during the battle waves
Enemy turn in the turn based part of the game (so that i don't cause lag during the "waiting for player turn" time)

In general everything that is not frame bound (so for example i would never move the units using a coroutine), could be a good canditate for their use. But again, there is no absolute rule, and each case has to be checked individually.

PS I am far from being a coroutines expert, but lately I am digging into them more and more, and they really saved my fps rate in several occasions.
Logged

Dr.Hogan-Lead Developer at H&R Games
------------------------------------------------------
Empires in ruins
Teknokommari
Level 0
**


View Profile WWW
« Reply #8 on: May 31, 2017, 01:34:39 pm »

The real beauty of coroutines is that they run "outside" the updates, and therefore they don't impact performances. Consider some risks they have, i.e. you will have to handle the sync with the update manually if that is needed, and also as already mentioned, consider the risk of having them hanging across time for too long.

A funny thing about unity updates is that you actually get better performance if you have single behavior handling the updates of all the game objects that need to be updated each frame.

Example:

Code:
//GameSessionBehavior.cs
public class GameSessionBehavior : MonoBehaviour
{
    List<UpdateMeBehavior> UpdateThese = new List<UpdateMeBehavior>();

    void Update()
    {
        for (int i = 0; i < UpdateThese.Count; i++)
        {
            if(UpdateThese[i].gameObject.activeInHierarchy)
                UpdateThese[i].CustomUpdate();
        }
    }

    public void Attach(UpdateMeBehavior upb)
    {
        if (null != upb && !UpdateThese.Contains(upb))
            UpdateThese.Add(upb);
    }

    public void Detach(UpdateMeBehavior upb)
    {
        if (UpdateThese.Contains(upb))
            UpdateThese.Remove(upb);
    }
}

Code:
//UpdateMeBehavior.cs
public class UpdateMeBehavior : MonoBehaviour
{
    //Called from GameSession
    public void CustomUpdate()
    {
        //update stuff here
    }

    void Start()
    {
        GameSessionBehavior s = FindObjectOfType<GameSessionBehavior>();
        if (s)
            s.Attach(this);
    }

    void OnDestroy()
    {
        GameSessionBehavior s = FindObjectOfType<GameSessionBehavior>();
        if (s)
            s.Detach(this);
    }
}

One thing to note too is that if the behavior is already using update function the small performance benefit has already been lost, because it'll then be called along with the co-routine unless you remove the update method from the behavior all-together.

I use state machines with my game characters so something like re-spawn would be called from DeadState of the character.
« Last Edit: May 31, 2017, 01:52:13 pm by Teknokommari » Logged

DrHogan
Level 0
**



View Profile WWW
« Reply #9 on: May 31, 2017, 08:44:53 pm »


A funny thing about unity updates is that you actually get better performance if you have single behavior handling the updates of all the game objects that need to be updated each frame.


Lately I was digging a bit deeper into optimization and the overhead cost of Update, did you see this one? 10000 Update() calls. On the other end, Unity is made to be flexible as every engine, and flexibility has a cost. I guess one should just make sure not to incur in extreme cases, and unless you're making some ultra-heavy computational game, most of the time you can still "ignore" the issue or at least not having problems with it.
Logged

Dr.Hogan-Lead Developer at H&R Games
------------------------------------------------------
Empires in ruins
bateleur
Level 10
*****



View Profile
« Reply #10 on: June 01, 2017, 11:48:04 pm »

A quick aside... if all you want to do is call a thing after a delay, you can use the Invoke method on MonoBehaviour.
Logged

DrHogan
Level 0
**



View Profile WWW
« Reply #11 on: June 02, 2017, 10:17:39 pm »

I just found this tutorial. The Zucconi guy is usually pretty good in explaining stuff in an understandable way, and this tutorial is quite useful to approach Coroutines. Enjoy!  Beer!

Nested Coroutines in Unity
Logged

Dr.Hogan-Lead Developer at H&R Games
------------------------------------------------------
Empires in ruins
PogueSquadron
Level 1
*



View Profile WWW
« Reply #12 on: July 03, 2017, 01:30:17 pm »

Thanks so much for all the help everyone. I can't wait to dive back into this.
Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic