DEV Community

Cover image for Creating a TypeWriter Effect in Phaser.js v3
JavaScript Joel
JavaScript Joel

Posted on • Edited on • Originally published at joel.net

Creating a TypeWriter Effect in Phaser.js v3

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.

Dungeon Sweeper: A Knights Adventure dialog animation

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;
      },
    });
  });
}
Enter fullscreen mode Exit fullscreen mode

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 🍻

Top comments (2)

Collapse
 
hendrikras profile image
Hendrik Ras

This is some really nice work, have you considered releasing it as a Phaser plugin?

Collapse
 
joelnet profile image
JavaScript Joel

I would, but I don't want to maintain it 😆