Modern browsers like Internet Explorer 10 are
implementing stable versions of some interesting HTML5 features, including offline
application programming interfaces (API), drag and drop, and file API.
These features are bringing us to a new era of web applications and fresh,
quickly emerging gaming scenarios.
In this two-part article, I’ll
show how I’ve used these new features to modernize my last HTML5 game, HTML5
Platformer.
Hopefully you’ll get some great new ideas for your own games!
- Part 1: Hardware
scaling and CSS3 (this article)
- Part 2: Offline , file, and drag-and-drop API (next article)
Note: The URL
demo is at the end of this article. Feel free to play using your favorite
browser, and check out the IE10 gameplay video. The source code will be
available for download in Part 2.
Scaling Across
Devices
If you’re building an HTML5 game,
you’re probably interested in the cross-platform nature of this standard
programming language. But compatibility with a broad variety of devices means
you have to take into account a huge number of resolutions. Compared to SVG,
Canvas—at first—seems ill-prepared to handle this.
However, with a casual game based
on sprites, there is a simple solution to implement. David Catuhe has done a
great job of describing this on his blog, Unleash
the power of HTML 5 Canvas for gaming – Part 1 (see
the section called "Using the hardware scaling feature" for specifics).
The idea is as
simple as it is smart. You’re working inside a canvas at a fixed,
predictable resolution, and you’re stretching it to the current displayed
resolution using the canvas.style properties.
Step 1: Stretch
In the case of my little HTML5
Platformer game, the assets and level logic have been set to 800x480. So if I
want to fill a 1080p screen or a 1366x768 tablet, I need to build higher
resolution assets to match those specs.
Before building all those assets,
I can try a scaling operation—along with proper anti-aliasing—to increase image
quality. Let’s give it a try.
The scaling operation simply requires
this code:
window.addEventListener("resize", OnResizeCalled, false);
function OnResizeCalled() {
canvas.style.width = window.innerWidth + 'px';
canvas.style.height = window.innerHeight + 'px';
}
And that’s it!
With hardware-accelerated
browsers, this operation will be done by your GPU at no cost. Anti-aliasing can
even be enabled. That’s why David Catuhe thought it was worth mentioning it in his Canvas
performance article.
This trick is not specific to
HTML5, by the way. Most modern console games are not internally computed in
720p or 1080p; almost all of them render in lower resolutions (like 1024x600)
and let the GPU handle the scaling/anti-aliasing process. In most cases, the
method described here can help you boost the number of frames per second (FPS).
But this action, by itself, raises
a ratio problem. Indeed, when the canvas had a fixed size of 800x480, the ratio
was controlled. Now, I get this strange result when resizing the browser
window:
The game is still playable, but also
clearly far from optimal.
Step 2: Control
Your Ratio
The idea here is to control how the
screen is filled when the browser window is resized. Rather than stretching
anything further, I’m 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. Here’s that code:
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";
}
The "if" statement creates an
exception: If you hit F11 in your browser to switch to full-screen viewing, and
you’ve got a 16:9 screen (like my 1920x1080 Sony VAIO Z screen or the 1366x768 Samsung
BUILD tablet), the game will be completely stretched. This experience was quite
awesome.
Without this exception, here is
the type of output you’ll see:
Notice the black areas below and
to the right of the game, controlling the ratio.
It would be even better if the
game was centered, giving it a widescreen movie effect, right? Let’s do it.
Step 3: Center the
Game with CSS3 Grid Layout
Centering an HTML element can
sometimes be painful. There are several ways to do it—and there are plenty of
resources on the web to help.
I like to use a new specification
named CSS Grid Layout (currently
only supported by IE10), which is the base of our Metro-style layout in Windows
8.
Centering an
Element with CSS Grid Layout is Straightforward
-
Switch
the display of the container to display:grid.
-
Define
1 column and 1 row.
-
Center
the inner element with the column-align and row-align properties.
Here’s 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 the prefix is "-ms"
for IE10. Mozilla
has recently annonced they will also support the CSS Grid
Layout specification for Firefox in 2012, which is excellent news. In the
meantime, this centering trick only works with IE10. Here’s what it looks like:
IE10 windows will display vertical
or horizontal black bars, similar to what you might see on a television screen.
In other browsers, the results will match those of Step 2, because the CSS3
Grid Layout specification will be ignored.
Using Smooth Animations
Now that we’re handling multiple
resolutions with an easy scaling operation, it would be nice to play a smooth
transition when the user is resizing the window. It would also be great to play
a cool animation while each level loads. For that, we’re going to use the CSS3
Transitions and CSS3 3D Transforms tools. On most platforms, hardware
acceleration is provided by the GPU.
Animating Every
Change Made to Canvas Style Properties
CSS3 Transitions is easy to use
and produces smooth, efficient animations. To discover how to use them, you can
read my colleague’s excellent article, Introduction
to CSS3 Transitions, or play around on
our Internet Explorer test-drive site, Hands On:
transitions.
I’ve set up a global transition for
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 "platformerCanvas"
ID will now automatically reflect any change made to its style properties in a
one-second animation generated by an easing function.
Thanks to this new rule, resizing
the browser window reduces/increases the size of the canvas with a smooth
animation. I love the effect it produces—all with only 3 lines of CSS.
Note: I also
add the different prefixes (-moz, -webkit, and -o for Mozilla, WebKit, and
Opera, respectively) for compatibility. You’ll want to remember to do the same.
Building a Cool
Animation Between Each Level
Now I’d like to use CSS3 3D
Transforms to temporarily make the canvas disappear. This will be done with an animated
90-degree rotation on the Y axis. I’ll load the next level once the animation rotates
90 degrees and comes back to its initial position (rotated zero degrees). To
better understand the effect, you can play with our Hands On:
3D Transforms
on the Internet Explorer test-drive site:
We’re also going to play with the
scale and rotateY properties to build a fun animation. For that, I’m adding two
CSS rules and targeting two classes:
.moveRotation
{
-ms-transform: perspective(500px) rotateY(-90deg) scale(0.1);
-webkit-transform: perspective(500px) scale(0);
-moz-transform: perspective(500px) rotateY(-90deg) scale(0.1);
}
.initialRotation
{
-ms-transform: perspective(500px) rotateY(0deg) scale(1);
-webkit-transform: perspective(500px) scale(1);
-moz-transform: perspective(500px) rotateY(0deg) scale(1);
}</</
First, my canvas has the initialRotation
class set:
<canvas id="platformerCanvas" width="800"
height="480" class="initialRotation"></canvas>
Now the idea is to wait until the
player beats the current level. Once that’s done, we’re changing the class of
the canvas from initialRotation to moveRotation. This will
automatically trigger the CSS3 Transitions we set before to generate the
animation.
In order to know when the
animation is finished, an event is raised. It’s named differently in each
browser. Here is the code I use for registering the event and targeting IE10,
Firefox, WebKit, and Opera:
PlatformerGame.prototype.registerTransitionEndEvents = function () {
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
called back:
function onTransitionEnd(instance) {
return function () {
if (instance.loadNextLevel === true) {
instance.LoadNextLevel();
}
}
};
At last, here is the code for my
game that set the moveRotation class on the canvas:
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 (Modernizr.csstransitions) {
this.loadNextLevel = true;
this.platformerGameStage.canvas.className = "moveRotation";
}
else {
this.LoadNextLevel();
}
}
else
this.ReloadCurrentLevel();
}
this.platformerGameStage.removeChild(statusBitmap);
overlayDisplayed = false;
}
this.wasContinuePressed = this.continuePressed;
};
Notice I’m using Modernizr to do a feature detection of CSS
Transitions. We’re finally setting back the initialRotation class inside
the LoadNextLevel function:
PlatformerGame.prototype.LoadNextLevel = function () {
this.loadNextLevel = false;
this.platformerGameStage.canvas.className = "initialRotation";
};
Note: You may
have noticed that I’m not setting the same transformation (and animation) for
WebKit as for IE10 and Firefox in my previous CSS block. This is because, in my
case, Chrome won’t behave the same way as IE10 and Firefox 11. I’ve set up a
simple reproduction case on jsFiddle here: http://jsfiddle.net/5H8wg/2/.
Demo Video and
URL
Here is a short video demonstrating
the IE10 features covered in this article:
You can also play with this demo
in IE10 or your favorite browser here: Modern
HTML5 Platformer.
In the next part of this article,
I’ll show how to implement offline API to make my game work without network
connections, and how drag-and-drop API can create another cool feature.