I recently added some dialog options for my indie game, Dungeon Sweeper: A Knights Adventure and wanted to create a TypeWriter effect for the dialog text.
This technique works well for monospaced fonts and may not work for variable-width fonts.
Failed Attempts
My first attempt was to write out text one letter at a time. But this didn't work with text that wasn't aligned to the left or text that wrapped.
My second attempt was to use a non-space white space to replace all characters. This would help with the non-left text and word-wrapped text, but then I learned not all white space is created equal.
The Figure Space
I found an amazing webpage that helped me find the correct white space to use. The space I need is called a Figure Space and is the same width as a character.
The Code
I wanted to be able to create a function that accepts a Phaser.GameObjecte.Text
and automatically applies the animation.
The function would maintain the state needed to run the animation.
The function would need to be awaitable, so the caller knows when the animation is complete.
The function works by first resetting the text
to empty. Then every interval, it writes out the next letter. To maintain the same letter positions, the text
is kept the same length. The unseen characters are replaced with invisible characters.
/**
* Create typewriter animation for text
* @param {Phaser.GameObjects.Text} target
* @param {number} [speedInMs=25]
* @returns {Promise<void>}
*/
export function animateText(target, speedInMs = 25) {
// store original text
const message = target.text;
const invisibleMessage = message.replace(/[^ ]/g, " ");
// clear text on screen
target.text = "";
// mutable state for visible text
let visibleText = "";
// use a Promise to wait for the animation to complete
return new Promise((resolve) => {
const timer = target.scene.time.addEvent({
delay: speedInMs,
loop: true,
callback() {
// if all characters are visible, stop the timer
if (target.text === message) {
timer.destroy();
return resolve();
}
// add next character to visible text
visibleText += message[visibleText.length];
// right pad with invisibleText
const invisibleText = invisibleMessage.substring(visibleText.length);
// update text on screen
target.text = visibleText + invisibleText;
},
});
});
}
Summary
I'm pretty happy with how the animation turned out and I am now calling this from within my dialog function.
If you are interested in following the game's development, toss your email into the newsletter box on http://joel.net.
Cheers 🍻