Reputation: 44
I'm new in Unity. Is it possible to save the current state of coroutine and load it then continue the coroutine? I have a few coroutines in my project and i want to save it all.
Upvotes: 0
Views: 1152
Reputation: 11120
When you define a Coroutine, eg;
using System.Collections;
public class C {
private void before() { }
private void after() { }
public IEnumerator MyCoroutine()
{
before();
yield return null;
after();
}
}
The compiler defines a new type to track the state of the method, including any local variables. You can see that in action by using a decompiler.
While it's more work, and more complicated, you could implement your own IEnumerable
types instead.
public class MyCoroutine : IEnumerator
{
private int state = 0;
public object Current { get; private set; }
private void before() { }
private void after() { }
public bool MoveNext()
{
switch (state)
{
case 0:
before();
state++;
Current = null;
return true;
case 1:
after();
state++;
break;
}
return false;
}
public void Reset() => throw new System.NotImplementedException();
}
Then it's up to you how you wish to save / load and resume your coroutines.
Upvotes: 1
Reputation: 20259
Unity doesn't have anything built in to support this. So, let's go over what saving a coroutine entails.
A started & incomplete coroutine has local variables, a yield instruction that they are currently at. And the yield that they are currently at may instantiate an anonymous YieldInstruction
.
So, if you would want to save a coroutine, you would need to save these things. So, you would need to save a bunch of locals as well as answer the question of how to resume the couroutine at the correct yield instruction. This is a complicated task, and the outcome would probably be very messy without substantial effort and refactoring.
So, I recommend that you accept the fact you will need to do some refactoring. Rather than keeping the coroutines intact, the more straightforward path here is to "unroll" your coroutines into the various update methods:
Update
, when using yield return null
, WaitForSeconds
, WaitUntil
, WaitForSecondsRealtime
FixedUpdate
, if using the uncommonly used WaitForFixedUpdate
LateUpdate
, when using WaitForEndOfFrame
and move the locals of the coroutine into fields, as well as any additional necessary fields needed to track time passage in place of WaitForSeconds
and such. And to make these fields so that they serialize properly.
Since there is no specific example given in the question, I will provide a before/after example that may help:
void Update()
{
DoStuff();
}
void StartMoveTowards(Transform target, Vector3 destination, float duration)
{
StartCoroutine(DoMoveTowards(target, destination, duration));
}
IEnumerator DoMoveTowards(Transform target, Vector3 destination, float duration)
{
float v = Vector3.Distance(target.position, destination) / duration;
while( target.position != destination)
{
yield return null;
target.position = Vector3.MoveTowards(target.position, destination, v * Time.deltaTime);
}
DoFirstThing();
yield return new WaitForSeconds(3f);
DoSecondThing();
}
enum MoveTowardsState
{
NotRunning,
Starting,
MovingTowards,
DoingFirstThing,
Waiting
}
// serializable coroutine locals, timers, and instruction state
[SerializeField] MoveTowardsState currentMoveTowardsState;
[SerializeReference]
[SerializeField] Transform moveTowardsTarget;
[SerializeField] Vector3 moveTowardsDestination;
[SerializeField] float moveTowardsDuration;
[SerializeField] float waitT;
[SerializeField] float v;
void Update()
{
DoStuff();
HandleDoMoveTowards();
}
void StartMoveTowards(Transform target, Vector3 destination, float duration)
{
moveTowardsTarget = target;
moveTowardsDestination = destination;
moveTowardsDuration = duration;
currentMoveTowardsState = MoveTowardsState.Starting;
}
void HandleDoMoveTowards()
{
bool continuing = true;
while(continuing)
{
continuing = false;
switch (currentMoveTowardsState)
{
case MoveTowardsState.NotRunning:
break;
case MoveTowardsState.Starting:
v = Vector3.Distance(target.position, destination) / duration;
if (target.position != destination)
{
currentMoveTowardsState = MoveTowardsState.MovingTowards;
}
else
{
currentMoveTowardsState = MoveTowardsState.DoingFirstThing;
continuing = true;
}
break;
case MoveTowardsState.MovingTowards:
target.position = Vector3.MoveTowards(moveTowardsTarget.position,
moveTowardsDestination, v * Time.deltaTime);
if (target.position == destination)
{
currentMoveTowardsState = MoveTowardsState.DoingFirstThing;
continuing = true;
}
break;
case MoveTowardsState.DoingFirstThing:
DoFirstThing();
waitT = 3f;
currentMoveTowardsState = MoveTowardsState.Waiting;
break;
case MoveTowardsState.Waiting:
waitT -= Time.deltaTime;
if (waitT <= 0f)
{
DoSecondThing();
currentMoveTowardsState = MoveTowardsState.NotRunning;
}
break;
}
}
}
And if multiple parallel instances are needed, You can create a new monobehavior for the coroutine, and create an instance for each running instance.
Note: I can't check syntax at the moment, so please mind any syntax errors. Hopefully the idea gets across. Please let me know in the comments if any parts dont make sense.
Upvotes: 0