Time Source : How It Works
On AmbienceSource
there are 2 choices of time source : Unscaled
(default) and Scaled
. This page explains how Tiny Ambience achieve it for all types of layers. The purpose of this article is... for your peace of mind. (That's why it is here in Advanced tab!)
I believe a plugin should have as few black boxes as possible. I hope you don't need this page in order to hot fix my bugs...
Unscaled
Intended behaviour is that ambience is unaffected when you change Time.timeScale
.
You may think "Of course not!" because you are used to AudioSource
which doesn't get messed up no matter what Time.timeScale
you use. But there are more to the party in Tiny Ambience that needs "fixing".
Layer Mode : Looping Clip
Looping Clip layer can have Speed Automation curve. The curve is run by legacy and lightweight Animation
component. Unlike the more costly Animator
component, this component do not have adjustable time source to be unscaled and by default is affected by Time.timeScale
.
To fix, Tiny Ambience counter-adjust the AnimationState
normalizedSpeed
as an inverse of the current Time.timeScale
to make it appears like nothing had happened. e.g. If time scale is 0.3, then animation play head speed becomes 1 / 0.3 = 3.33
, nullifying the time scale. Everything (volume, speed, pan) will automate as designed regardless of Time.timescale
.
Layer Mode : One Shot Program
One Shot Program use Time.realtimeSinceStartup
to determine when should the shot be fired. There is nothing to fix here since One Shot Program does not really have a "play head", it just waits and fires.
Layer Mode : Timeline
This is an easy one, we can just choose director mode UnscaledGameTime
and everything should work as intended.
Scaled
Intended behaviour is that ambience is affected when you change Time.timeScale
, that is, if you decrease Time.timeScale
to be below 1.00 the sound should be slow and stretched out, like horror sound. Using 0 Time.timeScale
will make the ambience stop.
Layer Mode : Looping Clip
The automation curve that is run by legacy and lightweight Animation
component is already affected by Time.timeScale
so we don't have to fix that.
But we have got new problems. AudioSource
does not get stretched out by Time.timeScale
by default, so to simulate the stretching out we need to modify the .pitch
accordingly.
That presents a new problem if the layer has Speed Automation curve. The .pitch
adjustment earlier will be overwritten by the curve. We have to make a new curve that has all keyframes scaled by the current Time.timeScale
to be correct. This curve will also run in slower/faster play head according to Animation
component's default behaviour.
Though, Tiny Ambience will modify the inside of AnimationCurve
instead of literally making a new curve. The AnimationCurve
is a class
and we could have GC allocation on every change of Time.timeScale
. Rather, Keyframe
inside the curve are struct
and would be released cleanly when going out of scope. We surgically replace all the Keyframe
inside.
Layer Mode : One Shot Program
Instead of using Time.realtimeSinceStartup
to determine where to fire the next shot, it collects Time.deltaTime
instead. The shot firing interval is now scaled to time.
To match the longer interval, all AudioSource
that would get PlayOneShot
then received .pitch
that would make them more stretched out when played. Everything related to audio length (e.g. "From Clip Length" interval mode) also needs the value increased/decreased accordingly.
Lastly, adjusting Time.timeScale
while a shot is firing may not produce a completely accurate result. For example, the audio maybe able to go from normal speed to slow speed mid-way, but the wait interval registered is already decided to use the normal one instead of slower one. Tiny Ambience make sure it at least sounds correct when you went crazy on Time.timeScale
adjustments. Timing maybe off a bit.
Layer Mode : Timeline
This is again, an easy one. We can just choose director mode GameTime
and everything should work as intended.