Smooth, Fast Coroutines? - c#

So I have a bunch of Coroutines running/working fine in my game.
However, one of them is for a power meter, and here's the issue...
You press the button, the power meter uses the coroutine to start filling up the power meter. (represented by the width of a red panel)
The goal is to make the meter fill up fast enough, as to where you can't just get "full power" every time. However, it also has to be smooth, and look nice.
The problem I'm facing is that my coroutine is maxed out on speed, and can't go any faster, and the meter is moving too slowly. So to "speed up" the meter, I have to use larger width increasing increments on the red panel.
It works, but, this causes 2 problems.
It doesn't look smooth. (It looks choppy and jittery)
It misses a whole bunch of values/possibilities. (e.g. The bar has to increase by a unit of "50" to make it fast enough to be difficult. But that's dumb, because that makes only 12 possibilities when stopping the meter. And there could be up to 600!)
Has anyone faced this before? If so, what's the best approach to making a fast, but smooth power meter, that can still hit a full range of value possibilities?
Thanks,
Ben
Here is my Code:
public static IEnumerator StartPitchMeter(Player.Guy guy, float duration)
{
var dur = 0.01f;
do
{
yield return new WaitForSeconds(dur);
PlayerControls.PitchMeter += 50f;
if (PlayerControls.PitchMeter > 600) PlayerControls.PitchMeter = 0f;
PlayerControls.pitchMeter.sizeDelta = new Vector2(PlayerControls.PitchMeter, 50);
} while (!PlayerControls.stopPitch);
}

If you're using a coroutine to update something for graphical purposes, you want to update on every available render frame (same as how often Update methods are called). So, instead of waiting for a set amount of time to pass, use yield return null and then move the meter according to Time.deltaTime, the time elapsed since the last frame in seconds. In this case, you can multiply Time.deltaTime by how many units per second you would like the meter to fill:
public static IEnumerator StartPitchMeter(Player.Guy guy, float duration)
{
do
{
yield return null;
// Fill 50 units per second.
float fillThisFrame = Time.deltaTime * 50f;
PlayerControls.PitchMeter += fillThisFrame;
if (PlayerControls.PitchMeter > 600) PlayerControls.PitchMeter = 0f;
PlayerControls.pitchMeter.sizeDelta = new Vector2(PlayerControls.PitchMeter, 50);
// Not sure what the 50 is doing here so it might be this instead:
// PlayerControls.pitchMeter.sizeDelta = new Vector2(PlayerControls.PitchMeter, fillThisFrame);
} while (!PlayerControls.stopPitch);
}

Related

Movement is dependent to frame-rate. How can i make it independent from frame-rate

i am pretty new to coding. till now i searched lots of things yet i still couldn't solve my problem. I am trying to make a gameObject follow a line/graph. i tried to give him a negative-Y velocity and adding small numbers(something like; update{mybody.velocity += 0.005}) each update to make it move. but i encountered a problem which is at low frame-rate my object is taking a super wide path and at higher frame-rate it take super tight path. how can i make my movement independent.
private float curve = 0;
private float curvemax = 2.3f;
[SerializeField]
private float curveupdate = 0.05f;
if (tracking.found && !hareketeBaşlandı)
{
curve = -(curvemax);
triggered1 = true;
hareketeBaşlandı = true;
print(curve);
}
if (transform.position.y != y1-curvemax && tracking.found)
{
curve += curveupdate;
print(curve+"Curving");
}
if (curve >= (curvemax))
{
curve = 0;
y2 = transform.position.y;
transform.position = new Vector3(transform.position.x, y1, transform.position.z);
tracking.found = false;
tracking.shouldsearch = false;
StartCoroutine("trackCD");
print("track off");
myBody.velocity = new Vector2(myBody.velocity.x, curve);
hareketeBaşlandı = false;
}
Note that using DateTime.Now and TimeSpan calculations as in this answer are quite expensive ... especially if you use it every frame just to calculate a delta in seconds.
This is actually exactly what Unity already provides in Time.deltaTime the time in seconds passed since last few was rendered .. so why calculate it complicated if we already know the value ;)
curve += curveupdate * Time.deltaTime;
In simple words multiplication by Time.deltaTime converts any value from "value per frame" into a frame-rate independent "value per second"
Instead of thinking about "rate of change," you must think about "rate of change per unit of time".
For example, you seem to be updating curve based on curveupdate = 0.05f;, which has a rate of change of 0.05f. You need to be thinking about how much rate of change per second. Assuming you currently get 0.05f change per frame, and let's say 10 frames per second, that means your rate of change per second is 0.5f (ten times higher) per second.
You then apply this amount multiplied by the number of seconds elapsed. Here's how to do it.
First when you kick off the animation, you need to remember when it started:
var lastUpdateTime = DateTime.Now;
Then change this:
curve += curveupdate;
To this:
DateTime currentTime = DateTime.Now;
float secondsElapsed = (float)(currentTime - lastUpdateTime).TotalMilliseconds / 1000F;
curve += (curveupdate * secondsElaped);
lastUpdateTime = currentTime;
This way, the amount of change scales depending on how much time has passed. If no time has passed, it will scale all the way down to 0, and if hours have passed, it will end up very very large. To an end user, the rate of change should seem consistent over time as a result.

How to cycle a coroutine every 10 seconds to accelerate a scrolling background?

I'm an absolute newbie in Unity, and I'm trying to create a 2D game that requires a scrolling background that accelerates every 10 seconds. I'm having trouble getting the code to work
I've tried to set up a Coroutine, but it seems to call the function every frame, instead of every 10 seconds
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScrollingBackground : MonoBehaviour
{
public float bgSpeed = 5;
public Renderer bgRend;
public float increment = 2f;
private void Start()
{
StartCoroutine(Accelerate());
}
void Update()
{
bgRend.material.mainTextureOffset += new Vector2(0f, bgSpeed * Time.deltaTime);
StartCoroutine(Accelerate());
}
private IEnumerator Accelerate()
{
while (true)
{
bgSpeed = increment * Time.deltaTime;
yield return new WaitForSeconds(10f);
Debug.Log("Getting Faster!");
Debug.Log("OnCoroutine: " + (int)Time.time);
}
}
}
Not only is the background speed going very slowly (only up to around 0.3 and stuck), I can't seem to make this work. Thank you for you help !
First, you should only start your coroutine once, such as in Start - instead, you're starting it every frame, so the speed is going to get stuck.
Second, is your texture scaled at all? This may affect how it looks when you're adjusting the offset directly, and thus why it appears to be stuck at 0.3 even though your increment is 2f here.
Finally, note that deltaTime is the time since the last frame, so using to adjust your speed rather than adding a constant is going to produce strange results dependent on performance. It's useful when applying the speed to movement, but not the acceleration based on this game logic.
It is calling your function every frame because you start the coroutine inside of Update. See Update scripting reference.

Slow collision detection at low frame rates

I'm experiencing an odd issue with my collision detection. I'm using the Update method to move the player (I don't want to use FixedUpdate because that creates an undesired weird movement). The fixed timestep is set at the default 0.02 (I tried playing with time setting but that didn't work either) . I set the collision detection of the rigidbodies of both objects to "continuous dynamic". Also, I set the target frame rate to 300 and that didn't change anything...
When the framerate is low or the device itself is slow, the collision detection doesn't always work. The player can easily fall through the object it's supposed to collide with, though sometimes it doesn't.
Please tell me what I can do to fix this because I've published a game and many users are reporting this (serious) bug. Thank you for your support.
This is what is supposed to happen:
This is what actually happens:
(as you can see, the cube gets out of the wall and to the other side)
I move the player when the user releases the mouse button:
Script 1:
public Script2 Jumper;
public float TimeToJump;
public void Update()
{
if (Input.GetMouseButtonUp(0))
{
StartCoroutine (Delay (1f/50f)); //Don't mind the time.
}
}
IEnumerator Delay(float waitTime)
{
yield return new WaitForSeconds (waitTime);
if (Jumper != null)
{
Jumper.SetVelocityToJump (gameObject, TimeToJump);
}
}
Script 2 attached to player (cube):
public class Script2 : MonoBehaviour {
GameObject target;
private float timeToJump;
public bool isJumping = false;
public void SetVelocityToJump(GameObject goToJumpTo, float timeToJump)
{
StartCoroutine(jumpAndFollow(goToJumpTo, timeToJump));
this.timeToJump = timeToJump;
this.target = goToJumpTo;
}
private IEnumerator jumpAndFollow(GameObject goToJumpTo, float timeToJump)
{
var startPosition = transform.position;
var targetTransform = goToJumpTo.transform;
var lastTargetPosition = targetTransform.position;
var initialVelocity = getInitialVelocity(lastTargetPosition - startPosition, timeToJump);
var progress = 0f;
while (progress < timeToJump)
{
progress += Time.deltaTime;
if (targetTransform.position != lastTargetPosition)
{
lastTargetPosition = targetTransform.position;
initialVelocity = getInitialVelocity(lastTargetPosition - startPosition, timeToJump);
}
float percentage = progress * 100 / timeToJump;
GetComponent<Rigidbody>().isKinematic = percentage < 100.0f;
transform.position = startPosition + (progress * initialVelocity) + (0.5f * Mathf.Pow(progress, 2) * _gravity);
yield return null;
}
OnFinishJump (goToJumpTo, timeToJump);
}
private void OnFinishJump(GameObject target, float timeToJump)
{
if (stillJumping)
{
this.isJumping = false;
}
}
private Vector3 getInitialVelocity(Vector3 toTarget, float timeToJump)
{
return (toTarget - (0.5f * Mathf.Pow(timeToJump, 2) * _gravity)) / timeToJump;
}
}
The target of the cube is a child of the bigger cube (the wall).
If you require clarification, please leave a comment below. I might give the link to my game if you need more details.
Quote from here (found thanks to #Logman): "The problem exists even if you use continuous dynamic collision detection because fast moving objects can move so fast that they are too far apart from itself from one frame to the next immediate frame. It's like they teleported and no collision detection would ever be triggered because no collision existed, from each frame perspective, and thus from all calculations processed."
In my case, the cube is not going fast, but you get the concept.
There are several issues with your code.
You are asking a Coroutine to yield for 1/50th of a second. The minimum time a yield must occur for is one frame. If Time.deltaTime > 0.02f this is already one of the problems.
You are using Coroutines and yield return null to compute physics calculations. Essentially, you're computing physics in Update(), which is only called once per frame (null is equivalent to new WaitForEndOfFrame(): as mentioned in (1), a running Coroutine cannot be yielding between frames). Under low frame-rate, the amount of motion an object undertook between two frames might exceed the collision range of the target trigger. Assuming linear, non-accelerating motion: ∆S = v∆t where v = velocity, ∆S is movement to cover in the current frame, ∆t is Time.deltaTime. As you can see, ∆S scales proportionally with ∆t.
You have GetComponent<T>() calls inside loops. Always avoid doing this: store a reference as a member variable instead (initialise it in Start()).
My suggestion for the quickest working hack would be to not worry too much about "being clean", and instead create subroutines that you call from FixedUpdate(), and (create and) use member bools to conditionally test which subroutine to "execute" and which to "skip". You can also use member bools or enums as triggers to switch between various "states".
A better solution would be to let Unity handle the kinematics and you instead work with rigidbody mutators (and not transform.positions), but that may be totally unnecessary for an arcade situation, which yours might be. In that case stick to the hack above.
If you really want to control kinematics by hand, use an engine like SFML. A Particle System tutorial would be a good place to start.
It's your float percentage, among other things.
"If isKinematic is enabled, Forces, collisions or joints will not affect the rigidbody anymore."
That's from the isKinematic page of Unity's documentation. You're setting it to true when progress hits 100. So at lower framerates, there'll be a sudden jump due to Time.deltaTime steps being a lot higher, progress is suddenly >= 100, isKinematic is set to true and the player is no longer affected by collisions.
I think you're going to have to rethink a lot of the code here and do some heavy optimisations. But the other posters have laid those out already, so I don't need to.
EDIT: Misunderstood the initial question, thought that it meant you were trying to detect collisions but your code wasn't always detecting them. Didn't realise it actually meant getting the collisions to occur in the first place.

Unity 3D Timer seems to be running too fast

When the user presses a button fwdi is set to 1 which starts this if statement. It seems logical to me that if I reduce fwdi by 0.1 every update and move the player 0.1 every update then everything should be done by the 1 second mark however the movement is much quicker than 1 second. I have an animation that also plays which takes 1 second and they don't match up.
EDIT
void Update ()
{
if (fwdi > 0.0f)
{
fwdi = fwdi - 1.0f * Time.deltaTime;
amountToMove.y = 1.0f * Time.deltaTime;
transform.Translate (amountToMove);
if (fwdi == 0.0f)
{
amountToMove.y = 0.0f;
fwdi = 0.0f;
}
}
}
All you should need to do in compensate for frametime (IE: at 60fps, update is called 60times per second)
void Update ()
{
if (fwdi > 0)
{
amountToMove.y = 0.1f*Time.deltaTime;
transform.Translate (amountToMove);
fwdi = fwdi - amountToMove.y;
//This code bellow looks redundant, and probably unnecessary
if (fwdi == 0)
{
amountToMove.y = 0;
fwdi = 0f;
}
}
}
Also consider putting movement on FixedUpdate. Fixed update works similarly to update with a few key differents:
-Fixedupdate has priority over the normal update function,
-unity default call cap for fixed update is 20 times per second
It's commonly suggested that you put, lightweight processes that benefit from predictable update intervals on fixed update, like movement and physics.
Note:: its still a good idea to multiply everything in FixedUpdate by Time.fixedDeltaTime, just in case Fixedupdate fails to reach its 20 tick per second count.
Time.timeScale
Frame rate
Time.deltaTime
Now I know almost nothing about Unity, so this may not be close to an answer. Normally you would make things in games frame rate independent by calculating a factor, say if you wan't to move 1 distance in one second, you calculated time passed and apply a multiplication. E.g. "timepassed-factor * amounttomove"
From what I can gather you need to use Time.deltaTime which would give you the actual time passed between frames, where as timeScale really just is for slow motion etc...
So... amountToMove.y = 1f * Time.deltaTime * ???;

sprite moves too fast after I unlimited the frame rate

I unlimited the frames per second in my game by doing.
graphics.SynchronizeWithVerticalRetrace = false;
IsFixedTimeStep = false;
But now my sprite/player moves WAY faster then it did before. I don't know why it does this, and I am not sure how to fix it.
if (keyboard.IsKeyDown(Keys.W) || keyboard.IsKeyDown(Keys.Up))
{
position.Y -= spd;
}
if (keyboard.IsKeyDown(Keys.A) || keyboard.IsKeyDown(Keys.Left))
{
position.X -= spd;
}
if (keyboard.IsKeyDown(Keys.S) || keyboard.IsKeyDown(Keys.Down))
{
position.Y += spd;
}
if (keyboard.IsKeyDown(Keys.D) || keyboard.IsKeyDown(Keys.Right))
{
position.X += spd;
}
That is currently how I am getting the sprite to move. spd = 4 at the moment. It worked perfectly fine, but now it seems as if it is moving like 2000 times faster. Just taping one of the keys takes him off screen.
Any and all help will be appreciated.
The game loop in XNA is based around update and draw. Fixed time step also refers to update, so by setting that to false, you are telling update to be called as often as possible. As your code is in the update function, its being called more than the default, fixed 60 times per second.
rather than just using spd, change it to
spd * (gameTime.ElapsedGameTime.Milliseconds / 16);
That will change it so that the spd is scaled by the elapsed time. The 16 is the number of milliseconds an update takes at 60 fps (approx) which is what your spd value is currently working at.
EDIT: For not part of Game.cs:
Have an update on the class you are interested in moving (Im going to call it Ship, but it could be anything you want)
class Ship
{
public void Update(GameTime gameTime)
{
...
position.Y += spd * (gameTime.ElapsedGameTime.Milliseconds / 16);
...
}
..
}
Then in the Game.cs file:
public override Update(GameTime gameTime)
{
myShip.Update(gameTime);
...
}
myShip being the variable for the class of the sprite you want to move. Note update is no longer overriding a base method, so the call to base.Update is also gone
When you turn off a fixed timestep, you need to take the time delta into the calculation to have any control over movement. For example:
if (keyboard.IsKeyDown(Keys.W) || keyboard.IsKeyDown(Keys.Up))
{
position.Y -= spd * gameTime.ElapsedGameTime.TotalSeconds;
}
...
In this case, you would set spd to the distance per second, not per frame.

Categories