I have a script in a game which stuns a target and throws them up into the air. The problem is, the yield return new WaitForSeconds(0.01f)
, and by extension the stun length, does not scale at all with Time.DeltaTime, even though it should. This causes the attack to permastun enemies if the game is sped up enough.
Here is the relevant code:
void Start()
{
coll = gameObject.GetComponent<Collider2D>();
halfLength = stunLength / 2;
baseColor = gameObject.GetComponent<SpriteRenderer>().color;
StartCoroutine("Gust");
}
public IEnumerator Gust()
{
coll.enabled = false;
float sus = halfLength;
while (halfLength > 0)
{
transform.position = new Vector3(0, halfLength * 0.05f, 0);
halfLength -= 0.03f;
yield return new WaitForSeconds(0.01f);
}
while (halfLength < sus)
{
transform.position = new Vector3(0, halfLength * -0.05f, 0);
halfLength = 0.03f;
yield return new WaitForSeconds(0.01f);
}
coll.enabled = true;
StunEnd();
}
The Time.TimeScale is being changed outside of the script, through a UI button. Any help would be greatly appreciated.
CodePudding user response:
Let's assume your game runs with 60
frames per second.
That means that the minimum time between two frames is already 1 / 60 = 0.016666...
.
So even in the default timeScale
a
yield return new WaitForSeconds(0.01f);
is already shorter than the default frame rate anyway and will yield
anyway => this basically equals using
yield return null;
So now if you speed up your game time even more it is even shorter and it will still equal
yield return null;
and yield
for at least one frame!
I hope you see now where this is going.
Instead of trying to use some fixed time intervals you should rather directly use frames and rely on the Time.deltaTime
.
You could try and rather use something like e.g.
If you want to go by duration and a specific position offset
// adjust this!
public Vector3 stunOffset = Vector3.up;
private IEnumerator MoveTo(Vector3 to, float duration)
{
yield return MoveFromTo(transform.position, to, duration);
}
private IEnumerator MoveFromTo(Vector3 from, Vector3 to, float duration)
{
for(var timePassed = 0f; timePassed < duration; timePassed = Time.deltaTime)
{
transform.position = Vector3.Lerp(from, to , timePassed / duration);
yield return null;
}
transform.position = to;
}
public IEnumerator Gust()
{
coll.enabled = false;
float sus = halfLength;
var initialPosition = transform.position;
var targetPosition = initialPosition stunOffset;
yield return MoveTo(targetPosition, halfLength);
yield return MoveTo(initialPosition halfLength);
coll.enabled = true;
StunEnd();
}
Or if you rather want to go by fixed move speed
// Adjust these
public float stunSpeed = 1f;
public Vector3 stunOffset = Vector3.up;
private IEnumerator MoveTo(Vector3 to, float speed)
{
while(tranform.position != to)
{
transform.position = Vector3.MoveTowards(transform.position, to, speed * Time.deltaTime);
yield return null;
}
transform.position = to;
}
public IEnumerator Gust()
{
coll.enabled = false;
float sus = halfLength;
var initialPosition = transform.position;
var targetPosition = (stunOinitialPosition stunOffset;
yield return MoveTo(targetPosition, stunSpeed);
yield return MoveTo(initialPosition stunSpeed);
coll.enabled = true;
StunEnd();
}
CodePudding user response:
Perhaps you should split the disabling of control & "animation" of crowd control into two separate coroutines.
IEnumerator DisableControl()
{
coll.enabled = false;
yield return new WaitForSeconds(stunLength);
coll.enabled = true;
}
IEnumerator Gust()
{
//Perform object manipulation/play animation
}
That way you can avoid misuse of waitforseconds and stunlocking the object. It also lets you potentially re-use the stun logic and use other "ways" of crowd-controlling without writing duplicate code