Facebook Twitter Gplus LinkedIn RSS
formats

Extended Unity Coroutines

If you’ve downloaded Unity Serializer you have an updated version of this already
Download

Further documentation on the now supported Unity Serializer version is available here.

I really like the coroutine pattern in unity but unfortunately it’s not possible to directly extend it. What I would really like is to be able to write cut scene and NPC logic in a single routine, but I need to do things like wait for an animation to complete or an AI task goal to be achieved. Not being able to write my own YieldInstructions I took to the keyboard and wrote an extended coroutine system that uses as Unity’s existing stuff as a base.

To use it you can download and install the package, then when you want to start an extended coroutine you use:

StartCoroutine( RadicalRoutine.Run(yourEnumeratorFunction()) )

YOU MUST insert the RadicalRoutine.Run inside the StartCoroutine call or not much will happen!

Note it only works with the none “text” based version of StartCoroutine at the moment.

Now you can write your own classes that indicate the completion status, they inherit from CoroutineReturn and can set or override two key values.

  • finished – set to true / or override and return true when the action has finished
  • cancel – set to true / or override and return true if you want the whole coroutine to be aborted

I’ve included one that waits for a part of an animation (end or some point in the middle) to complete and a version that waits for the weight of some animation to reach a value (I use this to keep my blended animations smooth in cut scenes). It’s called WaitForAnimation and there’s an extension class that adds methods to GameObject: gameObject.WaitForAnimation(name, normalizedTime /* default 1f */) and gameObject.WaitForAnimationWeight(name, weight /* default 0f */);

You can also yield any standard Unity YieldInstruction like WWW, WaitForSeconds etc.

You can use the coroutine functions from c# or javascript.

Example

Here’s an example of me using the extensions to run a complete sequence for an NPC. Obviously in this example I’m calling routines in my game that aren’t listed, but they all return CoroutineReturn instances that pause execution until the task is complete. You can see me using the WaitForAnimation and WaitForAnimationWeight methods directly.

What I want to do is move my character over to a table, pick up an envelope, show it to the player and then put it back down again afterwards.

	IEnumerator MoveToPosition_EnterState()
	{
		Vector3 envAngle;

		//Store the current position of the envelope to be picked up
		var rot = msg.transform.rotation;
		var parent = msg.transform.parent;
		var pos = msg.transform.position;
		//Find the point that will carry the envelope
		var ch = gameObject.GetComponentInChildren();

		//Move the character to the envelope's location
		yield return MoveTo((lookPt = msg.transform.Find("EnvelopeLook")).position);
		//Look at the envelope
		yield return RotateTo(envAngle = lookPt.rotation.eulerAngles);
		yield return new WaitForSeconds(0.3f);
		//Play the pickup animation
		Animate("pick_up_from_table");
		//Wait until half way through
		yield return gameObject.WaitForAnimation("pick_up_from_table",0.5f);

		//Put the envelope in the carry point
		msg.transform.parent = ch.transform;
		msg.transform.localPosition = Vector3.zero;
		msg.transform.localRotation = Quaternion.Euler(0,0,0);

		//Wait for the pickup animation to complete
		yield return gameObject.WaitForAnimation("pick_up_from_table");

		yield return new WaitForSeconds(0.2f);
		//Turn to face the camera
		yield return RotateTo(new Vector3(0,10,0));
		//Wait for the rotation animations to complete
		yield return gameObject.WaitForAnimationWeight("left_turn"); //Without a specified second parameter waits for 0
		yield return gameObject.WaitForAnimationWeight("right_turn");
		//Play the excited animation
		yield return Animate("excited");
		yield return new WaitForSeconds(0.5f);
		//Raise the hand containing the envelope
		yield return Animate("hand_raising");
		yield return new WaitForSeconds(1.5f);
		yield return Animate("hand_raising");
		yield return new WaitForSeconds(2.5f);
		//Wave move dramatically
		yield return Animate("waving");
		yield return new WaitForSeconds(0.5f);
		//Turn around to face the table
		yield return RotateTo(envAngle);
		//Play the pickup animation
		Animate("pick_up_from_table");
		//Wait for it to be 40% complete
		yield return gameObject.WaitForAnimation("pick_up_from_table",0.4f);
		//Put the envelope back on the table
		msg.transform.parent = parent;
		msg.transform.position = pos;
		msg.transform.rotation = rot;
		//Wait for the animation to complete
		yield return gameObject.WaitForAnimation("pick_up_from_table");
		//Rotate to face the camera
		yield return RotateTo(new Vector3(0,10,0));
		//Look over shoulder at envelope
		yield return Animate("looking_behind");
		//Sigh
		yield return Animate("relieved_sigh");
		yield return new WaitForSeconds(1);
		//Finish activity
		BlackboardComplete();

	}

Additional

You can also call RadicalRoutine.Create(yourCoRoutineFunction()) which returns an RadicalRoutine instance that can be cancelled. To use that you pass the instances enumerator property to StartCoroutine like this:

r = RadicalRoutine.Create(myRoutine());
StartCoroutine(r.enumerator);

r.Cancel(); //Abort on the next iteration

Also the RadicalRoutine has events for cancellation and completion called Cancelled and Finished.

5 Responses

  1. Bunny83

    This is a really simple and effective wrapper and the best thing is you finally have an interface which can be used to extend the yieldable objects. Personally i would also implement an extention function for GameObject (maybe for Component too) which directly takes the IEnumerator and wrapps it internally to StartCoroutine, So it can be executed as usual but instead of StartCoroutine you use “ExtStartCoroutine” or something like that ;)

    ExtStartCoroutine should of course return a RadicalRoutine and RadicalRoutine should also be derived from CoroutineReturn.

    Anyway, great piece of code :)

    • whydoidoit

      Agreed, my updated version of this includes a base MonoBehaviourEx class that lets you just use StartCoroutine (including the string version) – it’s now a part of Unity Serializer where that happens and there is an extension class rather like you suggest too – if you don’t want to use the base class.

  2. OnikiKay

    Hello,
    This seems really interesting, but I’m sorry to say that : This one of the worst examples I ever seen.
    Is it possible to have a “real” (functional) and simple example please?
    In your (complex) example you don’t even show the most important thing : How to use the class RadicalRoutine.

Home Project With Code Extended Unity Coroutines