Getting around the HTML5 canvas ctx.fillText line break limitation

Posted on July 27, 2012 | 3 minute read

Today I implemented a really cool plugin by quidmonkey. The plugin allows you to use Canvas-native fonts in an ImpactJS game. Out of the box, text in Impact is displayed through the use of images - there’s an image with each letter of your alphabet spaced out in a particular way, and you then draw parts of this image to put together letters in whatever order.

This plugin allows me to easily specify different fonts, sizes, colors, etc straight in the code without having to have an image for each font. It also allows you to set a velocity and fadeout time for the font. Basically, it’s just much more flexible than the native functionality.

The one problem I encountered is that Canvas’ fillText does not appear to support line breaks. I can’t just type n and start a new line. This is annoying for longer lines of text that don’t fit into my 320px-wide game window.

So I hacked together a bit of a makeshift solution for my placeholder text. As usual this is probably a clumsy way to do this, but it works.

First of all we’ll go over the two main functions in my Controller class that actually do all the work, then over where exactly they’re triggered. Code over-commented as usual for the purpose of this post.

    // Triggered when each level is loaded
    setDialogue: function() {
        switch (this.dialogueNum) {
            // So far only one set of placeholder dialogue snippets is in
            case 1: 	
                // Each instance of ~ in the text is a line break.
                this.dialogueArr = 
                    [{text:"Destroy the EVIL STUFF OF DOOM.~Quench the fire with icy water and stuff~and melt the ice with your kickass~flame-thrower.", x: 3, y: 380, color: '#00ff12', align: 'left', lifespan: 6},
                    {text: "But...wtf is happening, yo?", x: 317, y: 400,  align: 'right', color: '#ffffff', lifespan: 4},
                    {text: "Just do it, buddy.", x: 3, y: 400, align: 'left', color: '#00ff12', lifespan: 4}];
                break;
            }
        // When the array with dialogue lines is ready,
        // trigger the function that spawns the actual text
        this.triggerDialogue();
    },






    // The function that actually spawns the dialogue on the screen. 
    triggerDialogue: function() {
        var currentLine = this.dialogueArr[0];
        // Create an array of strings from the current dialogue piece, splitting at each '~'
        var lines = currentLine.text.split('~');
        var settings = {align: currentLine.align, color: currentLine.color};
        // For every line we created with the split above, spawn the string
        for (var i = 0; i < lines.length; i++) {
            var currentText = lines[i];
            ig.game.fm.spawn(currentLine.lifespan, '11px Lucida Console', currentText, currentLine.x, currentLine.y,  settings);
            // Moving down by 12 pixels for each line
            currentLine.y += 12;
            // Trigger the next line only after this line has faded out
            ig.game.dialoguetimeout = currentLine.lifespan;

        } 
        // Remove this first dialogue piece from the array,
        // making the subsequent dialogue piece spawn next
        this.dialogueArr.shift();
    }

The setDialogue function above is called at the beginning of each relevant level. The second function (triggerDialogue) is called once after the setDialogue function runs and also in main.js like this:

        // If it's time for the next piece of dialogue to come up
        if (this.DialogueTimer.delta() > 0) {
            // And the dialogue array is not empty
            if (ig.game.Controller.dialogueArr.length > 0) {
                // Trigger the dialogue and reset the dialogue timer
                ig.game.Controller.triggerDialogue();
                this.DialogueTimer.set(this.dialoguetimeout);
            }
            // If the dialogue array is empty
            else {
                // Reset and pause the dialogue timer. 
                this.DialogueTimer.reset();
                this.DialogueTimer.pause();
            }
        }

Like I said, this is probably really messy and uses quite a few globals so please let me know if you have specific improvement suggestions. It’s functional for what I need it for, anyway.




Categories:game dev dev games
comments powered by Disqus