- Home /
[Rhytm game] How to spawn on exact time of music?
I'm working on a simple rhytm game, where enemies spawn to the rhytm of music (maps are premade in form of scriptable objects). Now, as it's a rythm game I need to spawn those enemies in the exact time of the song, otherwise they won't be in sync...
SongStartTime = AudioSettings.dspTime; // in a coroutine that starts a music
SongCurrentTime = AudioSettings.dspTime - SongStartTime; // in Update
if (SongCurrentTime >= _spawnTime) //in Update as well
{
Debug.Log(SongCurrentTime);
_spawnController.SpawnNote();
CurrentNoteIndex++;
RecalculateSpawnTime();
}
From that debug.log ( and also some tests) I know that the problem is in AudioSettings.dspTime not updating fast enough ( if game is played on 600 fps, I get a mismatch of around 10ms which is already visible), so I need to get another way of spawning enemies on the exact (like 1ms off) time of the song...
DSP time should be sample accurate so I doubt its that. You are sampling it at non specific intervals. Assu$$anonymous$$g everything else is correct. Have you tried running your code in FixedUpdate Ins$$anonymous$$d?
Also:
Who has a screen that can render 600fps?
Can the spawned items be buffered (Pre spawned) into place?
Can you give more of a description of the mechanic/ gameplay please?
I ment that update loop is executing 600 times per second (once per frame).
Yeah they will be prespawned and added to objectPoll.
Game is basicly a copy of $$anonymous$$useDash : https://youtu.be/_rNTCySxaYY (obviously it won't be publicly aviable).
And with the current method of spawning, the problem is in a fact that AudioSettings.dspTime is updating like ~200 times per second or so (that alone gives at least 5 ms of lag)... Pic describes what I mean :
Im at work at the moment but I will try to mock something up later when I get off work.
Answer by TonicMind · Aug 16, 2019 at 01:51 PM
So the problem you're having here is:
SongStartTime = AudioSettings.dspTime; // in a coroutine that starts a music
SongCurrentTime = AudioSettings.dspTime - SongStartTime; // in Update
The above is essentially zero. AudioSettings.dspTime - SongStateTime has to be zero, as they are the same value.
So in your if statement you end up with a greater than or equal condition, and since zero is the result the above code (in all cases) the _spawnTime variable is either negative or zero.
On top of that you are not using the difference in spawn times. You simply call _spawnController.SpawnNote() without changing the time of spawn.
To accomplish your goal:
public IEnumerator DelayNoteSpawn()
{
startTime = AudioSettings.dspTime; //Save the time of start for the note
float difference = Time.sinceLevelLoad - startTime; //Find the difference between then and now
yield return new WaitForSeconds(difference); //Wait 'difference' seconds
_spawnController.SpawnNote(); //Spawn your enemy/note.
}
Hope this helps!
They wont be the same if its a value type. Which it is i.e. a double and time has passed.
https://docs.unity3d.com/ScriptReference/AudioSettings-dspTime.html
Um... what? You don't understand the code I have written nor do you understand what you're talking about.
Interesting position. Ok so you said:
So the problem you're having here is:
SongStartTime = AudioSettings.dspTime; // in a coroutine that starts a music
SongCurrentTime = AudioSettings.dspTime - SongStartTime; // in Update
The above is essentially zero. AudioSettings.dspTime - SongStateTime has to be zero, as they are the same value.
Why would they be the same value?
Thanks for reply, but you're wrong here : SongStartTime = AudioSettings.dspTime; is called only once, at the start of whole gameplay, the code above is not in the same block, I just pasted it so (and commented) because to show whole code I would have to paste few classes each having like 60 lines or so...
The problem is not that I can't get it to spawn correctly (in a way), the problem is that I need it to be exact perfect with music, and right now I have around 10ms of inconsistency
Answer by sacredgeometry · Aug 16, 2019 at 02:45 PM
Reading the documentation. There is a code example:
https://docs.unity3d.com/ScriptReference/AudioSettings-dspTime.html
It would appear that you need to read the value inside the context of this method
OnAudioFilterRead()
https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnAudioFilterRead.html
Well, I don't think it's the case, since I can read dspTime without an error (it's just that it's updated not frequently enough). And from docs of <OnAudioFilterRead That callback is called like every ~20ms, so that's way, way too inprecise.
The access doesn't change depending on where you reference it as its public but the amount of times it gets called and the amount of tertiary information you get does.
Have a look at the metronome example. All you are doing thats different is accenting pre deter$$anonymous$$ed beats and having it raise an event on that accent.
Alternatively there are Unity packages
https://www.youtube.com/watch?time_continue=59&v=DTgn_b$$anonymous$$CWyg
That handle this for you (that one isnt cheap though).
Oh p.s. I have a colleague that solved a very similar problem I can add more specific information once he is available.
Answer by Magso · Aug 16, 2019 at 08:58 PM
You could divide the BPM by 60 and check if dspTime is a multiple of that number.
if (AudioSettings.dspTime % (60f / bpm) == 0){
//spawn
}
dspTime is a double type value... Let's say a song has a 180 bpm, divide by 60, and we have 3 beats per second. So now if at a time that the check is going, if the dspTime is not "with the beat" then code should wait 1/3 second only for the next check if It goes with the beat? I't would only introduce further inconsistancies to spawning, what if I can't get the right value in dspTime for several frames in a row, It could delay spawn of one note by even seconds... I'm starting to feel like I should completely change the way my "spawning" works... (I think I should spawn them all at Start(), and position them in a way that they arrive at right time...
You would still need to trigger them some how unless you developed some sort of playhead.
i.e. a constantly moving collider that triggers enemies it collies with may work.
Oh p.s. I decompiled that package and there isn't anything in there that hasn't been said here
Wouldn't you still have the same problem? You said that dspTime wasn't updating fast enough. Have you tried using OnAudioFilterRead()? There's also a thread here which if I'm not mistaken is discussing the same problem.
You wouldnt need dsp time if you did it that way.