I am creating a rythm VR game for google cardboard, inspired by Beat Saber on PSVR and Oculus Rift.
The concept is that blocks with different directions come at you, following the rythm of the music, just like in Beat Saber.
For every music I have created a ScriptableObject called music, with every parameter it should contain like the difficulty of the song, the name, etc. but also an array of Vector3 called notes : the first float of the Vector corresponds to the time the note should be beaten in the rythm of the music starting from 0: ScriptableObject
Then in a script called SlashSpawnManagement, where every thing is based on WaitForSeconds(), I spawn the blocks to smash. It's realy hard for me to explain wih words the order I do it in, so here is an image : Explanation
In theory, what this script does, it waits for some time, spawns a block, waits for some time, spawn a block, etc. The logic seems okay, but here's the weird part. As you play the song the distance between each block gradually becomes bigger and bigger, meaning the blocks become more and more out of sync with the music. It starts very well, but at the end of the music there is at least a 5s gap.
I figured it has something to do with the frame rate drop, so I tried to set the frameRate to something low with :
QualitySettings.vSyncCount = 0; // VSync must be disabled
Application.targetFrameRate = 30;
But it doesn't solve the problem. I tried with WaitForSecondsRealtime instead of WaitForSeconds, but nothing changes. I have read somewhere that WaitForSeconds depends on the frame rate... Where I calculated the time, I tried sustractng h divided by a number to even out the gradual gap. This works for some blocks but not every block: Notes[j][0] - h / 500
So here's my question, how do I make the WaitForSeconds, or any other method consistent with the seconds provided ?
Thank you In advance,
PS : For more clarifications, please ask and please forgive my typos and my english :)
If you want something to happen in regular time intervals, it is important to make sure that errors don't accumulate.
Don't:
private IEnumerable DoInIntervals()
{
while (this)
{
yield return new WaitForSeconds(1f); // this will NOT wait exactly 1 second but a few ms more. This error accumulates over time
DoSomething();
}
}
Do:
private IEnumerable DoInIntervals()
{
const float interval = 1f;
float nextEventTime = Time.time + interval;
while (this)
{
if (Time.time >= nextEventTime)
{
nextEventTime += interval; // this ensures that nextEventTime is exactly (interval) seconds after the previous nextEventTime. Errors are not accumulated.
DoSomething();
}
yield return null;
}
}
This will make sure your events happen in regular intervals.
Note: Even though this will be regular, it does not guarantee that it will stay in sync with other systems like audio. That can only be achieved by having a shared time between systems, like spender mentioned in his comment on your question.
Trying to use WaitForSeconds the timing of audio and hope for the best is hoping a big hope.
Have a list of Vector3s prepared in advance. If you want to prepare the list using the rythm - it will work. Use AudioSource's time to check every Update whether the timing is right and spawn a block at that moment.
void Update () {
SpawnIfTimingIsRight();
}
void SpawnIfTimingIsRight() {
float nextTiming = timingsArray[nextIndex].x;
// Time For Block To Get To Position
float blockTime = this.blockDistanceToTravel / this.blockSpeed;
float timeToSpawnBlock = nextTiming - blockTime;
if (this.audioSource.time >= nextTiming && (this.audioSource.time - Time.deltaTime) < nextTiming) {
// Spawn block
}
}
Related
Next code below acts different on Android, while in Editor it acts as supposed:
private IEnumerator CountTo(int target)
{
yield return new WaitForSeconds(0.5f);
int start = 0;
do
{
ScoreText.text = string.Format(scoreText, start);
if (start != 0)
{
SoundManager.instance.Play(ScoreSound, default, 0.4f);
}
start++;
yield return new WaitForSeconds(0.33f);
}
while (start <= target);
}
In editor, when it called, it waits for 0.5 seconds before launch (to wait Complete level sound to play), counts to target one by one with playing ScoreSound each time (except the first one) when score changes.
On Android it firsts plays all of the the needed ScoreSound sounds and only then, SLOWLY (almost like with lags) changes the UI score. In debugger, step by step, everything works fine. Using the Profiler, FPS is stable when this happens.
Any idea what can be causing this?
Thanks!
OK, guys, sorry, I figured it out, the answer is actually really stupid... So it turns out that this was happening because of the Post Processing Stack's Depth of Field, it was activated during Level Complete phase and when the CountTo was called, so it seems that these two somehow interfered each other, so I've got what I've got.
Sorry again! Thanks!
When I run the game, Unity's inspector updates every few seconds, but totalTimeElapsed only increments in a series of 0.1 seconds. I tried a similar thing with totalTimeElapsed += Time.deltaTime but with the same result.
How do I get the total time since game start?
float totalTimeELapsed = 0;
int dayCount = 0;
private void Update()
{
totalTimeElapsed = Time.time;
dayCount += 1;
AFunctionTakingLotsOfTime();
}
Because AFunctionTakingLotsOfTime() takes a long time, you need Time.realtimeSinceStartup
Time.realtimeSinceStartup deals in real time, rather than computed frame-rate-time amounts that are probably getting messed up due to the fact that your function takes a long time.
Alternatively you could use DateTime.
Note that both will not be affected by things like time scale and you may want to change your function to be less intensive, such as computing only a small chunk any given call (or by using a coroutine) or by splitting the method off onto a Job thread.
float totalTime = 0f;
// Update is called once per frame
void Update () {
totalTime += Time.deltaTime;
}
I believe what you are looking for would be something like this. Assuming this class is present on runtime of your game, you should have an accurate variable which can tell you the elapsed time since the game started.
You might also want to know that the update call occurs once per frame, ie. if you are running at 60 FPS update will be called 60 times in that second. You can try FixedUpdate which updates in real-time intervals instead.
Trying to make accurate replay system in unity and c#
Hi all,
Im working on a racing game and I decided to add a replay system to allow "ghost car" too, initially I was recordng data in some events like key pressed but only recording that data in all frames I manage a smooth replay, well its still ok as file is not huge and replay works, but the trouble is there is always a slight variation in time, only like 0.1 seconds or 0.2 at the most, I have a list of keyframes and in each position I record a time to be shown, the trouble I think is that because fps vary then not in all runs the same time marks are shown then the winning frame's time is not always being rendered so the winning frame happens in next update slightly after it should be shown. Im using c# and unity just in case, but I think its independent to this mainly. Thanks a lot about any clue, I have been around this issue for some time now
It sounds like you're doing frame-by-frame replay which, as you've discovered, requires your frames to play back with the same delay as the recording. In a game-render loop, that's not guaranteed to happen.
As you record the car states (position, heading, etc) per frame, you need to also record a timestamp (in this case, accumulating Time.deltaTime from race start should suffice).
When you play it back, find the current timestamp and interpolate (ie, Lerp) the car's state from the recorded bounding frames.
Hints for frame interpolation:
class Snapshot {
public float Timestamp;
public Matrix4x4 Transform; // As an example. Put more data here.
}
private int PrevIndex = 0;
private List<Snapshot> Snapshots = (new List<Snapshot>()).OrderBy(m => m.Timestamp).ToList();
private float GetLerpFactor(float currentTimestamp) {
if ( PrevIndex == Snapshots.Count - 1)
return 0; // Reached end of Snapshots
while (currentTimestamp >= Snapshots[PrevIndex + 1].Timestamp)
PrevIndex++; // move 'frame' forward
var currentDelta = Mathf.Max(0f, currentTimestamp - Snapshots[PrevIndex].Timestamp);
var fullDelta = Snapshots[PrevIndex + 1].Timestamp - Snapshots[PrevIndex].Timestamp;
var lerpFactor = currentDelta / fullDelta;
return lerpFactor;
}
Unless for some reason you need to interact with "ghost" car (like collisions) record final data on position/speed/direction at frequent enough original moments of time and interpolate that to new simulation. I would not record raw inputs but rather resulting changes (like gear shifts) unless you need to measure/show how fast user reacted to something.
If you really want to replay the same inputs you'd have to run two separate simulations at the same time so physics and timing of "real" version don't impact "ghost" one, most likely you'll have to again interpolate output of "ghost" simulation to align with real one (unless you have fixed time steps).
TL,DR: is it safe to use Time.deltaTime as the wait time for the yield in a coroutine?
Often to avoid unnecessary logic inside Update() for short lived functionality I'll run a coroutine instead. For example in my game I have a slow coroutine that periodically checks the distance between NPCs and the player, if that distance is under some threshold I'll then run a more frequent coroutine with more expensive line of sight testing using raycasting etc. This seems much more efficient than checking everything constantly in Update().
My question is this: is it safe to use Time.deltaTime as the wait time for the yield, thereby simulating Update() for the duration of the coroutine? i.e. yield return new WaitForSeconds(Time.deltaTime);
Since deltaTime is not constant what happens if the next frame takes longer than the last - is the coroutine delayed until the next frame, or is something horrible happening like the whole frame being delayed until the coroutine finishes? I'm tempted to allow a buffer for safety e.g. yield return new WaitForSeconds(Time.deltaTime * 1.2f); but ideally I'd like it to execute precisely every frame.
is it safe to use Time.deltaTime as the wait time for the yield in a
coroutine?
No. That's not how to use the WaitForSeconds function. WaitForSeconds takes in seconds as a parameter not the tiny values provided by Time.deltaTime in every frame.
Below is an example of how to use the WaitForSeconds function.
IEnumerator waitFunction1()
{
Debug.Log("Hello Before Waiting");
yield return new WaitForSeconds(3); //Will wait for 3 seconds then run the code below
Debug.Log("Hello After waiting for 3 seconds");
}
As for waiting with Time.deltaTime, you usually use it in a while loop in addition to another float variable you will increment or decrement until you reach the wanted value.The advantage of using Time.deltaTime is that you can see how much waiting time is left while waiting. You can use that for a countdown or up timer. You also put yield return null; in the while loop so that Unity will allow other scripts to run too and your App won't freeze. Below is an example of how to use Time.deltaTime to wait for 3 seconds. You can easily turn it into a countdown timer.
IEnumerator waitFunction2()
{
const float waitTime = 3f;
float counter = 0f;
Debug.Log("Hello Before Waiting");
while (counter < waitTime)
{
Debug.Log("Current WaitTime: " + counter);
counter += Time.deltaTime;
yield return null; //Don't freeze Unity
}
Debug.Log("Hello After waiting for 3 seconds");
}
If you want to check every frame there are WaitForEndOfFrame
There is no point to WaitForSeconds if you don't need specific time to wait for
You can also yield return www to wait for it finish
Update, there are CustomYieldInstruction and some of it inheritance for used in coroutine, like WaitUntil
I know that this is a bit old, but I just came across it today while looking for something that's related.
Anyways, Programmer's answer is a good start. Using Time.detlaTime is not a good way to release control of a coroutine. For one, using a null value will get you to the next frame in simulation. Second, what happens if the frame you just finished was a bit slow (or the next frame is a bit fast). Please keep in mind that the simulation (or game) runs in frames, much like the frames in a film. Because of this, using WaitForSeconds can have some interesting effects. For instance, if the time you requested happens between frames, what do you think will happen? The coroutine will regain control after the requested time. So, if you were hoping to just shoot off a WaitForSeconds(Time.deltaTime) on each loop in the coroutine, chances are, you are skipping the occasional frame (sometimes more).
I had a coworker that did a bunch of WaitForSeconds, inside a long running coroutine and the total time that passed was anywhere between 5 to 10 seconds longer than what was expected. Those off by part of a frame errors in the requested time will add up, and unlike nature, where errors tend to average out, these errors will always add up. That's because the amount of time that passes will always be greater-than or equal-to the amount of time that's requested.
Programmer's answer showed you how to use Time.deltaTime to get the passage of simulation time. I should warn you that once you start using things other than null to release control of the coroutine, that Time.deltaTime might stop working for you.
If you use a null follow by some code followed by a WaitForEndFrame, you will still be within the same frame when the coroutine resumes control. Because you are still in the same frame as you were before calling WaitForEndFrame, the simulation time that has passed will be zero, but Time.deltaTime will not be zero. So, yeah, measuring time can get tricky once you start mixing different return types. You should also be careful of switching between frames and fixed-frames. Using a WaitForFixedUpdate will put you into the times of the fixed-frames. Calling Time.fixedDeltaTime after a WaitForFixedUdpate will give you the time between that fixed-frame and the previous fixed-frame, which has nothing to do with Time.deltaTime or any of timings for the frames.
Update: I have uploaded a video showing the stutter here: http://intninety.co.uk/xnastutter.mp4 you may have to look closely in the video if you are not viewing it at 1920x1080, but you'll see that there is a rather distinct stutter when moving every 2 seconds or so, I'd recommend viewing it in Windows Media Player rather than your web browser to ensure the video itself isn't choppy and thus preventing you seeing the actual stutter
I'm recently picking up a project I started a while ago, however I am still struggling to solve the problem I left it at!
At the moment I have a very simple application which just has a single sprite on screen and is moved around using the directional keys. The problem is every two seconds or so, the game stutters and the sprite appears to jump backwards and then back forwards very quickly.
The sprite itself is a 55x33 bitmap, so isn't anything large, and the code in use is as follows. Hopefully this is enough to get the ball rolling on some ideas as to what may be the problem, if a video is required to see exactly how the stuttering looks I can put one together and upload it somewhere if need be.
As you'll see in the code it does compensate for time lost between frames by making the movement greater should it happen, however that drop is happening very consistently time wise, which is leading me to believe I'm doing something wrong somewhere.
I've tried on a few different machines but the problem persists across all of them, if anyone has any ideas or can see where it is I'm messing up it'd be greatly appreciated if you could point it out.
Thanks :)
Constructor of the Game Setting up the Graphics Device Manager
graphics = new GraphicsDeviceManager(this);
graphics.IsFullScreen = true;
graphics.SynchronizeWithVerticalRetrace = false;
graphics.PreferredBackBufferWidth = 1920;
graphics.PreferredBackBufferHeight = 1080;
Content.RootDirectory = "Content";
this.IsFixedTimeStep = false;
Code from the Game's Update Method
KeyboardState keyboard = Keyboard.GetState();
GamePadState gamePad = GamePad.GetState(PlayerIndex.One);
if (keyboard.IsKeyDown(Keys.Escape)) {
this.Exit();
}
if ((keyboard.IsKeyDown(Keys.Left)) || (gamePad.DPad.Left == ButtonState.Pressed))
{
this.player.MoveLeft((float)gameTime.ElapsedGameTime.TotalMilliseconds);
} else if ((keyboard.IsKeyDown(Keys.Right)) || (gamePad.DPad.Right == ButtonState.Pressed))
{
this.player.MoveRight((float)gameTime.ElapsedGameTime.TotalMilliseconds);
}
if ((keyboard.IsKeyDown(Keys.Up)) || (gamePad.DPad.Up == ButtonState.Pressed))
{
this.player.MoveUp((float)gameTime.ElapsedGameTime.TotalMilliseconds);
} else if ((keyboard.IsKeyDown(Keys.Down)) || (gamePad.DPad.Down == ButtonState.Pressed))
{
this.player.MoveDown((float)gameTime.ElapsedGameTime.TotalMilliseconds);
}
base.Update(gameTime);
The "Move" Methods seen in the above Update Method
public void MoveLeft(float moveBy)
{
this.position.X -= (moveBy * this.velocity.X);
}
public void MoveRight(float moveBy)
{
this.position.X += (moveBy * this.velocity.X);
}
public void MoveUp(float moveBy)
{
this.position.Y -= (moveBy * this.velocity.Y);
}
public void MoveDown(float moveBy)
{
this.position.Y += (moveBy * this.velocity.Y);
}
The Game's Draw Method
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.Draw(this.player.Texture, this.player.Position, null, Color.White, this.player.Rotation, this.player.Origin, 1.0f, SpriteEffects.None, 0.0f);
spriteBatch.End();
base.Draw(gameTime);
Edit: forgot to mention, the velocity object used in the Move methods is a Vector2
I've managed to see it occur once for a split second which has led me to what I think is the problem. Since you are using the raw ElapsedGameTime.TotalMilliseconds value as a factor for your movement, all computer lag that your program experiences will be directly applied to the motion. For example, if your computer (OS) does something else for one twentieth of a second, then the elapsed time value will accumulate to a value of ~50 milliseconds, when it is normally about 0.3 milliseconds. This would cause a frame that has 150 times more motion than a normal frame.
To cause this to happen manually, you can do the following:
// define a frame counter
private int mCounter;
...
protected override void Update(GameTime pGameTime)
{
// save the elapsed time value
float time = (float)pGameTime.ElapsedGameTime.TotalMilliseconds;
...
// force a long frame every 2500th frame (change depending on your framerate)
if (mCounter++ > 2500)
{
mCounter = 0;
time = 75; // about 225 times longer frame
}
...
// use the time value in your move calls
if ((keyboard.IsKeyDown(Keys.Left)) || (gamePad.DPad.Left == ButtonState.Pressed))
mPlayer.MoveLeft(time);
To prevent this from happening, (aside from setting IsFixedTimeStep = true;, which would fix it immediately; but assuming you want IsFixedTimeStep to be false), you should use a time value, as above, but cap it. It's up to you to determine the proportion of elapsed time to motion and to determine what is a good amount of time to allow to pass per frame. Ex:
protected override void Update(GameTime pGameTime)
{
// save the elapsed time value
float time = (float)pGameTime.ElapsedGameTime.TotalMilliseconds;
if (time > 1)
time = 1;
...
if ((keyboard.IsKeyDown(Keys.Left)) || (gamePad.DPad.Left == ButtonState.Pressed))
mPlayer.MoveLeft(time);
...
While that will correct the issue for your current program, your frames are only at 0.3ms each since there is not a lot happening. Once there is more to your game, more time will be elapsing per frame, and you will want the cap to be much higher than 1ms.
EDIT: To be clear, this is 'downtime' from your CPU/OS. It's not going to go away*, it's just up to you whether to jump ahead a bunch when it happens (which can cause problems if the elapsed time ever makes it to 2000ms, for example), or to cap these spikes and let them cause lag; either way there is going to be a 'hole' in your motion that you can't fill. This really is normal, and it's not as critical as it seems. Once there is more happening in your game, it will become less and less noticeable. It stands out strongly at the moment particularly because there are only two graphics present and nothing else happening.
*(Actually, you might look for other applications and processes that you can close to keep the CPU from being borrowed by some other program, but since you are using a multi-tasking OS, you are never going to be guaranteed to have the CPU to yourself.)
Update Again
Just had a thought. Have you ever checked that (float)gameTime.ElapsedGameTime.TotalMilliseconds does not result in Infinity or a negative number? The elapsed time becoming negative would explain your sprite jumping back then forward.
Its the keyboard. You can prove this by changing moving left and right to left mouse and right mouse click. Use the keydown and keyup triggers to set a state instead of iskeydown.
Update:
It appears there are a few things that cause "stuttering" in XNA. I thought for sure your issue was the keyboard problem one. However, I can only assume you've tested your code with the GamePad also and it suffers the same.
There is another problem with IsFixedTimeStep but you already have that set to false. A quick google search suggests there are more than a few people with these types of problems without any clear fix.
There is a long discussion at http://forums.create.msdn.com/forums/t/9934.aspx?PageIndex=6
Other things to look at:
Try restricting your frame rate to 60 fps. There isn't any reason to go above that anyways. One power suggests that a 2d app that does nothing could stall the graphics pipeline with a significantly high throughput.
Check to see if IsSlowRunning is ever called. Garbage collection and Chaos Theory could cause this to be set. I