I have a coroutine DefaultFixedUpdate()
that functions as a replacement for FixedUpdate()
and another coroutine called WallSlidingFixedUpdate()
. I have two variables that store which of the both is active. activeCoroutine
and prevActiveCoroutine
with the starting strings nameof(DefaultFixedUpdate)
and "no coroutine"
. In FixedUpdate()
from Unity I have this code that should change coroutines if activeCoroutine
and prevActiveCoroutine
are not the same:
void FixedUpdate()
{
if (activeCoroutine != prevActiveCoroutine)
{
print($"changing coroutines from {prevActiveCoroutine} to {activeCoroutine}");
StopCoroutine(prevActiveCoroutine);
StartCoroutine(activeCoroutine);
prevActiveCoroutine = activeCoroutine;
print("changed coroutines");
}
}
When starting the game DefaultFixedUpdate()
starts as expected and prevActiveCoroutine
changes as expected to activeCoroutine
and when a condition inside DefaultFixedUpdate()
is met to switch to WallSlidingFixedUpdate()
activeCoroutine
changes to the new string as expected. But when StartCoroutine(activeCoroutine)
gets called the code inside WallSlidingFixedUpdate()
doesn't run for some reason. StopCoroutine(prevActiveCoroutine)
does work. The end result is that none of the two corutines runs but expected was that WallSlidingCoroutine()
runs.
These are the two Coroutines. The changing of activeCoroutine
happens almost at the bottom of both functions.
IEnumerator DefaultFixedUpdate()
{
while (true)
{
//setup
gravity = (velocity.y < 0 || !Input.GetKey(KeyCode.Space)) ? dropGravity : normalGravity;
Vector2 pos = transform.position col.size.y * 0.5f * Vector3.up;
Vector2 timeStepVelocity = velocity * Time.fixedDeltaTime;
Vector2 pos1 = pos;
velocity.y -= gravity * Time.fixedDeltaTime;
//checking collision
RaycastHit2D[] colChecks = Physics2D.BoxCastAll(pos, col.size, 0, timeStepVelocity, timeStepVelocity.magnitude, 0b1000000);
//remove unwanted collisions that are behind and don't make sense to collide with
List<RaycastHit2D> newColChecks = new();
foreach (RaycastHit2D colCheck in colChecks)
if (Vector2.Dot(colCheck.normal, timeStepVelocity) < 0)
newColChecks.Add(colCheck);
colChecks = newColChecks.ToArray();
//remove unwanted velocities
foreach (RaycastHit2D colCheck in colChecks)
{
if (Mathf.Abs(colCheck.normal.x / colCheck.normal.y) > 1) // left and right
{
velocity.x = Mathf.Sign(colCheck.normal.x) > 0 ? Mathf.Max(velocity.x, 0) : Mathf.Min(velocity.x, 0);
}
else // top bottom
{
velocity.y = Mathf.Sign(colCheck.normal.y) > 0 ? Mathf.Max(velocity.y, 0) : Mathf.Min(velocity.y, 0);
}
}
//calculate correct destination
if (colChecks.Length == 0)
pos = timeStepVelocity;
else
{
//create bounding box for all possible future positions
Vector4 posBox = new Vector4(pos.x, pos.x timeStepVelocity.x, pos.y, pos.y timeStepVelocity.y);
//move to surface
pos = colChecks[0].centroid;
//slide along surface until hitting another tangent or the bounding box
Vector2 tangent = new Vector3(colChecks[0].normal.y, -colChecks[0].normal.x);
Vector2 centroid = pos;
//slide until hitting distance bounds
if (Mathf.Abs(tangent.x) > Mathf.Abs(tangent.y)) //top and bottom
{
pos = tangent / tangent.x * (timeStepVelocity.x - pos.x pos1.x);
tangent = Mathf.Sign(centroid.y - pos1.y) * tangent;
}
else //left and right
{
pos = tangent / tangent.y * (timeStepVelocity.y - pos.y pos1.y);
tangent = Mathf.Sign(centroid.x - pos1.x) * tangent;
}
//save previous surface that is being slid on
Vector2 prevTangent = Tangent(colChecks[0].normal);
//check if I collided with another tangent along the way
colChecks = Physics2D.BoxCastAll(centroid, col.size, 0, pos, Vector2.Distance(pos, centroid), 0b1000000);
//remove unwanted collisions that are behind and don't make sense to collide with
newColChecks = new();
foreach (RaycastHit2D colCheck in colChecks)
if (Vector2.Dot(colCheck.normal, tangent * Mathf.Sign(Vector2.Dot(pos - centroid, tangent))) < 0 || Vector2.Dot(colCheck.normal, timeStepVelocity) < 0)
newColChecks.Add(colCheck);
colChecks = newColChecks.ToArray();
if (colChecks.Length > 1)
{
pos = centroid (pos - centroid).normalized * colChecks[1].distance;
}
//repeat
int loopBreak = 0;
string loopBreakMessage = "";
while (true)
{
loopBreakMessage = centroid "\n";
if (loopBreak > 4)
{
Debug.LogError("loop limit exceeded\n" loopBreakMessage);
break;
}
loopBreak ;
//check if I collided with another tangent along the way
colChecks = Physics2D.BoxCastAll(centroid, col.size, 0, pos - centroid, Vector2.Distance(pos, centroid), 0b1000000);
//remove unwanted collisions that are behind and don't make sense to collide with
newColChecks = new();
foreach (RaycastHit2D colCheck in colChecks)
if (Tangent(colCheck.normal) != prevTangent)
newColChecks.Add(colCheck);
colChecks = newColChecks.ToArray();
if (colChecks.Length > 2)
{
prevTangent = colChecks[0].normal;
if (!VectorOnSameQuarter(colChecks[1].normal, colChecks[2].normal)) // stuck in a corner
{
pos = centroid (pos - centroid).normalized * colChecks[2].distance;
break;
}
else //continue along new tangent
{
//corner pos
centroid = (pos - centroid).normalized * colChecks[2].distance;
//end pos if there's no surface along the way
pos = PosAlongVectorInsideRectangle(posBox, centroid, Tangent(colChecks[2].normal));
}
}
else
{
break;
}
}
AlignVelocityWithGround();
}
//check if player is grounded and save ground tangent to remove small hops when going down slopes
PlayerState.OnGround = false;
RaycastHit2D[] jumpChecks = Physics2D.BoxCastAll(pos, col.size, 0, Vector2.down, 1e-3f, 0b1000000);
if (jumpChecks.Length != 0)
{
foreach (RaycastHit2D jumpCheck in jumpChecks)
{
if (jumpCheck.normal.y > Mathf.Abs(jumpCheck.normal.x))
{
PlayerState.OnGround = true;
groundTangent = new Vector2(jumpCheck.normal.y, -jumpCheck.normal.x);
}
}
PlayerState.state = PlayerState.state == PlayerState.State.WallSliding ? PlayerState.State.Default : PlayerState.state;
//check for a wall slide and save wall tangent for smooth sliding
if (!PlayerState.OnGround && velocity.y < 0 && Mathf.Abs(jumpChecks[0].normal.x) > Mathf.Abs(jumpChecks[0].normal.y) && Mathf.Asin(jumpChecks[0].normal.y) * Mathf.Rad2Deg < 15)
{
groundTangent = new Vector2(jumpChecks[0].normal.y, -jumpChecks[0].normal.x);
transform.position = pos - col.size.y * 0.5f * Vector2.up;
activeCoroutine = nameof(WallSlidingFixedUpdate);
yield break;
}
}
transform.position = pos - col.size.y * 0.5f * Vector2.up;
yield return new WaitForFixedUpdate();
}
}
IEnumerable WallSlidingFixedUpdate()
{
print("wallsliding coroutine entered");
while (true)
{
print("wallsliding coroutine happening");
Vector2 pos = transform.position;
pos -= Time.fixedDeltaTime * wallSlideSpeed * groundTangent / groundTangent.y;
transform.position = pos;
if (InputManager.Left && groundTangent.y > 0)
{
activeCoroutine = nameof(DefaultFixedUpdate);
yield break;
}
else if (InputManager.Right)
{
activeCoroutine = nameof(DefaultFixedUpdate);
yield break;
}
yield return new WaitForFixedUpdate();
}
}
CodePudding user response:
Holy moly, how did I miss this. The return value of WallSlidingFixedUpdate
is IEnumerable
and not IEnumerator
. I changed it to IEnumertor
and now it works.
CodePudding user response:
You figured your issue about the return type out already -> It needs to be IEnumerator
.
Anyway my question is WHY?
Instead of going through all that trouble for switching between different Coroutine instances via a string
I would rather have only a single outer scope routine running and then rather switch between which inner block to execute
private enum RoutineType
{
Default,
WallSliding
}
private RouineType activeRoutine;
private IEnumerator FixedUptadeRoutine()
{
while(true)
{
switch(activeRoutine)
{
case RoutineType.WallSliding:
yield return DefaultRoutine();
break;
case default:
yield return WallSlidingRoutine();
break;
}
}
}
private IEnumerator DefaultRoutine()
{
while(true)
{
// NOTE: Btw you want to wait for fixed update FIRST
// Before applying all the values
...
// And now this will simply make sure you change the routine type
activeRoutine = RoutineType.WallSliding;
// and exit this one so the outer routine will handle the switch in the next frame
yield break;
...
}
}
private IEnumerator WallSlidingRoutine()
{
...
}
you can simply yield return
any IEnumerator
so it will be executed and at the same time the outer routine waits for it to finish. There is no need to have multiple start and stop coroutines.