This is a situation that comes up a lot in game development; It recently reared its head again when adding a 'ghost writer' text effect to our NPC dialogue system.
I thought I would take a moment to talk about the crux of the issue so that some folks might understand it better.
When developing a 'ghost writer' text feature, I found myself first doing a word for word string build up.
An update event would happen, and I would simply move my nextWordIndex forward by one.
To throttle this I had a countdownTimer, which, when a new word was shown, I added an amount to; and would decay this timer in the update method.
So to recap, new word shown, timer set, timer elapses, show next word.
My co-developer Brian, saw this effect and felt it was too choppy; instead he wanted it to spit out single characters, instead of discrete words.
I made some quick modifications to test this, and found that adjusting my CPS (characters per second) did nothing up to a point.
Since I have seen this effect before, I quickly realized what was wrong.
Because in my update method, I would check for timer expiration, and perform the wordIncrement (now character increment) this meant, I could NEVER achieve faster word incrementing than One Character per Frame.
At a 1:1 ratio this is considered 'Drive' the input shaft (the game loop) is turning the output shaft (the cps incrementor) at 100%.
When I was doing words per second I was working at a comfortable Under-Drive, or, a ratio less than 1:1; such that multiple discrete frames would have to pass before the next word was shown.
[sub]I often relate programming to real-world mechanics, in this case, some may see that a game-loop, to me feels a lot like a drivetrain of some machine.[/sub]
Simply increasing my CPS in an effort to speed up the incrementor saturated into 1CPS.
The root of the problem here, is that I am relying on a timer expiration and a single fixed increment of an index.
This conditional discrete operation means that it is fixed to a maximum of the frame rate.
Thankfully the solution is very simple
First, it probably makes sense to generalize how we are displaying the ghost-written string; to instead of string-building, using a sub-string instead:
Imagine text.subString(0,Math.floor(ll)); where 0 is the starting index, and ll is lineLength; the number of characters to display (with a max of text.length, of course).
[sub]Note the Math.floor(), ll is a floating point variable (this is essential) but we can only display integer length strings, so a truncation is needed.[/sub]
With this, we can remove our countdownTimer machinery, and instead, simply 'drive' ll:
ll += frameTimeSeconds * CPS;
We can see above that we push ll forward using our measured frame time, and a constant 'Characters Per Second' the throttle by which you wish to print characters.
a typical under-drive scenario might be when we are achieving a frame time of 0.016 or 60FPS; meaning each frame is executing at 1/60th of a second.
If we imagine a CPS of 1 (one character printed per second) we see that we will push ll forward only 0.016 'characters' per frame: this is under-drive because the game loop is 'rotating' multiple 'revolutions' to only a single 'revolution' (character increment).
In an over-drive scenario; let us imagine we are operating with a low frame rate '0.033' (30FPS); and a higher CPS of 40 (40 characters printed per second).
[sub]1.32 = 0.033 * 40[/sub]
We can see here we are now pushing ll one whole character and about 1/3 of a character per frame, this means we are operating at a ratio over 1:1, or 'Over-drive'.
The important effect here is that we are preserving non-integral time accumulation, instead of just clamping/saturating it away, as over-drive increases, we could/would begin to see multiple discrete characters being printed per-frame, in order to 'keep up'.