Udostępnij za pośrednictwem


Modernizing your HTML5 Canvas games Part 1: hardware scaling & CSS3

Latest versions of the browsers start to implement stable version of very interesting HTML5 features like Offline APIs, Drag’n’drop & File APIs. Those new features offer new gaming scenarios for the web developers and bring us to a new web application era. On our side, IE10 implements all of them and the same features are also available inside Windows 8 HTML5 Metro Style Apps. On the graphics’ side, the IE10 engine offers also a powerful hardware acceleration graphics layer than could be very useful for handling various resolutions scaling and playing smooth animations. We will concentrate first on this graphics part in this article by using CSS3 Grid, CSS3 Transition, CSS3 3D Transform

In these 2 articles, we’ll see how I’ve used these new features to modernize my previous HTML5 game named HTML5 Platformer. I hope that these 2 tutorials will bring you some new ideas for your own games.

- Modernizing your HTML5 Canvas games Part 1: hardware scaling & CSS3 (this article)
- Modernizing your HTML5 Canvas games Part 2: Offline APIs, File APIs & Drag’n’drop APIs (next article)

Note: You’ll find the URL of the complete demo to play with your favorite browser at the end of this article as well as a video of the result inside IE10.  The source code will be available for download at the end of the next article. The game has been tested with success under IE10, Firefox 15, Chrome 21 and Opera 12.

Scaling across devices

If you’re building a HTML5 game, it’s probably because you’re interested in the cross-platform nature of this standard. But running on a huge variety of devices raises the question of the variety of resolutions. Compared to SVG, Canvas could be first seen as not very well prepared for this scenario.

Hopefully, with a casual-like game based on sprites, there is a simple solution to implement. It has been very well described by my friend David Catuhe on his blog here: Unleash the power of HTML 5 Canvas for gaming – Part 1 in the “using the hardware scaling feature” section.

The idea is pretty simple & smart. You’re working inside a canvas at a fix & predictable resolution and you’re stretching it to the current displayed resolution using the canvas.style properties.

Step 1: stretch

In my case with my little HTML5 Platformer game, the assets & level logics have been set with a 800x480 resolution in mind. So, if I’d like to fill a complete 1080p screen or a 1366x768 tablet, I need to build higher resolution assets to match these resolutions. Otherwise, I can use a scaling operation which, combined with proper anti-aliasing, could provide enough quality. Let’s try this.

This scaling operation is simply done by this code:

 window.addEventListener("resize", OnResizeCalled, false);

function OnResizeCalled() {
    canvas.style.width = window.innerWidth + 'px';
    canvas.style.height = window.innerHeight + 'px';
}

Or even simpler via a CSS rule like:

 #canvas 
{
    width: 100%;
    height: 100%;
}

But you’ll understand later on why I’m registering to the resize event of the window.

And that’s it! With hardware accelerated browsers, this operation will be done by your GPU at no cost! You will even have anti-aliasing enabled. Indeed, the scaling operation is done by the GPU thanks to the 100% applied to the width & height properties. Recent browsers also automatically apply an anti-aliasing algorithm for you. That’s why, David Catuhe was mentioning it in his canvas performance article.

This is not specific to HTML5 by the way. Most of the modern console games are not internally computed in 720p or 1080p. Almost all of them render the game in lower resolutions (such as 1024x600 for instance) and let the GPU doing the scaling/anti-aliasing process. This could help you boosting the FPS in most cases.

But simply doing that raises a ratio problem. Indeed, when the canvas had a fix size of 800x480, the ratio was controlled. Now, I can obtain this strange output while resizing the browser’s window:

image image

The game is still playable but the experience is far from being optimal.

Step 2: control your ratio

The idea here is to control how to fill the screen when the browser’s window is resized. Rather than fully stretching, we’re going to add some empty space on the right if the window is too large or at the bottom if the window is too high. This is done by this code in my case:

 var gameWidth = window.innerWidth;
var gameHeight = window.innerHeight;
var scaleToFitX = gameWidth / 800;
var scaleToFitY = gameHeight / 480;

var currentScreenRatio = gameWidth / gameHeight;
var optimalRatio = Math.min(scaleToFitX, scaleToFitY);

if (currentScreenRatio >= 1.77 && currentScreenRatio <= 1.79) {
    canvas.style.width = gameWidth + "px";
    canvas.style.height = gameHeight + "px";
}
else {
    canvas.style.width = 800 * optimalRatio + "px";
    canvas.style.height = 480 * optimalRatio + "px";
}

I’ve added an exception with the if statement. If you’re pressing F11 in your browser to switch in full screen and you’ve got a 16/9 screen (like my 1920x1080 Vaio Z screen or the 1366x768 Samsung BUILD tablet), the game will be completely stretched. I’ve found that this experience was quite awesome. Clignement d'œil

Otherwise, here is the kind of output you’ll now have:

imageimage

Notice the black areas below or on the right of the game. It’s here to control our ratio. But it would be better if the game was centered to have a widescreen movie effect, wouldn’t be?

Step 3: center the game with CSS3 Grid

Centering an HTML element could be painful in certain scenarios. There is several ways to do that and you’ll find plenty of resources on the web to help you. I’d like to use a new specification named CSS Grid Layout currently only supported by IE10 and which is the base of our Metro Style layout in Windows 8. Centering an element with CSS Grid is straightforward. You just have to switch the display of the container to display:grid, define 1 column & 1 row and center the inner element with the colum-align & row-align properties.

Here the the CSS used in my case:

 .canvasHolder {
    width: 100%;
    height: 100%;
    display: -ms-grid;
    -ms-grid-columns: 1fr;
    -ms-grid-rows: 1fr;
}

#platformerCanvas {
    -ms-grid-column: 1;
    -ms-grid-row: 1;
    -ms-grid-column-align: center;
    -ms-grid-row-align: center;
}

You’ll notice it’s currently prefixed by “-ms” for IE10. Mozilla has recently annonced  they will also support the specification in Firefox in 2012 which is excellent news! In the meantime, this solution currently only works with IE10 and here is the kind of output generated:

imageimageimage

In IE10, like on your TV screen, you will have vertical or horizontal black bars. On the other browsers, you will have the same result as the step 2 as the CSS3 Grid Layout will be simply ignored by them.

Using Smooth Animations

Now that we’re handling multiple resolutions via an easy scaling operation, it would be nice to play a smooth transition when the user is resizing the window. It would be nice also to play a cool animation during each level loading. For that, we’re going to use CSS3 Transitions & CSS3 3D Transforms. It’s hardware accelerated by the GPU on most platforms.

Animating every change done to the canvas style properties

CSS3 Transitions is easy to use and produce efficient & smooth animations. If you’d like to discover how to use them, you can read my colleague’s excellent introduction article: Introduction to CSS3 Transitions or play with it on our IE Test Drive: Hands On transitions.

In my case, I’ve setup a global transition on all my canvas’ properties thanks to these rules:

 #platformerCanvas {
    -ms-grid-column: 1;
    -ms-grid-row: 1;
    -ms-grid-column-align: center;
    -ms-grid-row-align: center;

    -ms-transition-property: all;
    -ms-transition-duration: 1s;
    -ms-transition-timing-function: ease;
}

The canvas with the id “platformerCanvas” will now automatically reflect any change done to its style’s properties via a 1s animation using an easing function. Now, thanks to this new rule, if you’re resizing the browser’s window, a smooth animation will be played to reduce/grow the the size of the canvas. I love the effect produced. Just by adding only 3 lines of CSS! CSS3 transition is really awesome for that.

Note: I’ve also added the prefixes versions –moz, –webkit & –o for Mozilla, Webkit & Opera to be compatible with the other browsers. Never forget to do the same!

Building a cool animation between each level

I’d like now to use CSS3 3D Transform to disappear temporally the canvas. This will be done via an animation of a 90 degrees rotation on the Y axis. Then, we will load the next level once the animation will reach the 90 degrees and come back to the initial state of 0 degree. To better understand the effect, you can play with our Hands On 3D Transforms on the IE Test Drive site :

image

We’re going also to play with the scale property in conjunction with the rotateY property to build a fun animation. For that, I’m adding 2 CSS rules targeting 2 classes:

 .moveRotation
{
    -ms-transform: perspective(500px) rotateY(-90deg) scale(0.1);
    -webkit-transform: perspective(500px) rotateY(-90deg) scale(0.1);
    -moz-transform: perspective(500px) rotateY(-90deg) scale(0.1);
    -o-transform: scale(0);
}

.initialRotation
{
    -ms-transform: perspective(500px) rotateY(0deg) scale(1);
    -webkit-transform: perspective(500px) rotateY(0deg) scale(1);
    -moz-transform: perspective(500px) rotateY(0deg) scale(1);
    -o-transform: scale(1);
}

And my canvas has first the initialRotation class set:

 <canvas id="platformerCanvas" width="800" height="480" class="initialRotation"></canvas>

Now the idea is to wait until the player has won the current level. Once done, we’re changing the class of the canvas from initialRotation to moveRotation. This will then automatically triggers the CSS3 Transition set before to generate the animation.

In order to know that the animation is finished, there is an event raised. It’s named differently in each browser. Here is my code registering to the event & targeting IE10, Firefox, Webkit & Opera:

 // Registering to the various browsers vendors transition end event
PlatformerGame.prototype.registerTransitionEndEvents = function () {
    // IE10, Firefox, Chrome & Safari, Opera
    this.platformerGameStage.canvas.addEventListener("MSTransitionEnd", onTransitionEnd(this));
    this.platformerGameStage.canvas.addEventListener("transitionend", onTransitionEnd(this));
    this.platformerGameStage.canvas.addEventListener("webkitTransitionEnd", onTransitionEnd(this));
    this.platformerGameStage.canvas.addEventListener("oTransitionEnd", onTransitionEnd(this));
};

And here is the code that will be call-backed:

 // Function called when the transition has ended
// We're then loading the next level
function onTransitionEnd(instance) {
    return function () {
        if (instance.loadNextLevel === true) {
            instance.LoadNextLevel();
        }
    }
};

At last, here is the code of my game which set the moveRotation class on the canvas:

 // Perform the appropriate action to advance the game and
// to get the player back to playing.
PlatformerGame.prototype.HandleInput = function () {
    if (!this.wasContinuePressed && this.continuePressed) {
        if (!this.level.Hero.IsAlive) {
            this.level.StartNewLife();
        }
        else if (this.level.TimeRemaining == 0) {
            if (this.level.ReachedExit) {
                // If CSS3 Transitions is supported
                // We're using smooth & nice effects between each levels
                if (Modernizr.csstransitions) {
                    this.loadNextLevel = true;
                    // Setting the moveRotation class will trigger the css transition
                    this.platformerGameStage.canvas.className = "moveRotation";
                }
                // If CSS3 Transition is not supported, we're jumping directly
                // to the next level
                else {
                    this.LoadNextLevel();
                }
            }
            else
                this.ReloadCurrentLevel();
        }
        this.platformerGameStage.removeChild(statusBitmap);
        overlayDisplayed = false;
    }

    this.wasContinuePressed = this.continuePressed;
};

Notice that I’m using Modernizr to do a feature detection of CSS Transitions. We’re finally setting back the initialRotation class inside the LoadNextLevel function:

 // Loading the next level 
PlatformerGame.prototype.LoadNextLevel = function () {
    this.loadNextLevel = false;
    // Setting back the initialRotation class will trigger the transition
    this.platformerGameStage.canvas.className = "initialRotation";
    // ... loadNextLevel logic stuff...
};

Note:   Opera doesn’t support CSS3 3D Transform yet.

Video & URL to play this demo

Here is a short video of IE10 demonstrating the features implemented in this article:





Poster Image

Download Video: MP4, WebM, HTML5 Video Player by VideoJS

You can also play with this demo in IE10 or your favorite browser here: Modern HTML5 Platformer

image

In the second article, we’re going to see how I’ve implemented the Offline APIs to make the game works without network connections and how I’ve used File & Drag’n’drop APIs to implement a cool feature in my game.

David

Comments

  • Anonymous
    April 06, 2012
    No -o-?

  • Anonymous
    April 06, 2012
    Hmm, in which browsers does your Code run fine. window.addEventListener("resize", OnResizeCalled, false); function OnResizeCalled() {    canvas.style.width = window.innerWidth + 'px';    canvas.style.height = window.innerHeight + 'px'; } why i need to code a simple function? A workin layout System has this build in. What a shame with your your HTML guys.

  • Anonymous
    April 06, 2012
    Note: I’ve also added the prefixes versions –moz, –webkit & –o for Mozilla, Webkit & Opera to be compatible with the other browsers. Never forget to do the same! I think we talk about standards? Aou do not work in standards, you work in draft an properitary Languages.

  • Anonymous
    April 08, 2012
    Using the word "anti-aliasing" to describe upscaling an image is really stretching the terminology. Please don't do that.

  • Anonymous
    April 08, 2012
    @hoopz: the anti-aliasing is done for you by the gpu in this case. That's why I'm using this terminology. Maybe I should have more clear in my article. David

  • Anonymous
    April 08, 2012
    The comment has been removed

  • Anonymous
    April 08, 2012
    @Jens: well, some CSS3 specs before being finalized must be prefixed. It's still a standard but it's a draft. Moreover, in my case, it doesn't break in other browser thanks to degradation

  • Anonymous
    April 09, 2012
    @test, I've modified the article to add a 100% width & height rule. But I'm registering to the resize event this way to prepare later code that handles the ratio. David

  • Anonymous
    April 11, 2012
    Very Good article David, congratulations. Too bad there are many annoying people commenting here.

  • Anonymous
    April 13, 2012
    Thanks Alexei! :)

  • Anonymous
    June 12, 2012
    Nice Article David...

  • Anonymous
    July 03, 2012
    Very well done. My favorite part is that you can make the player moon walk by pressing 'd' then the left arrow key, or the right arrow key then 'a'.

  • Anonymous
    August 02, 2013
    This is a great article. After porting my iPhone game to Android I was afraid I was going to have to have to apply a scale to every element being rendered. Good work!

  • Anonymous
    January 28, 2014
    Nice Article... It will really help me for my project. :-) Thanks

  • Anonymous
    May 31, 2014
    Nice Article !! I have the problem of my " lag animation game" .I have a drag and drop game using about 10 bitmap animation and large of bitmap size file. Can you see my code for a moment? To see what the core of my "lag game" Here's the link . www.mediafire.com Please run in your local server . I'm so looking forward to your solution. Many thanks . Regards, bobby