As announced on the Windows blog this morning, today Pirates Love Daises - a new HTML5 tower defense game - has been released!
Grant Skinner, the leader of the team that built the game, reported his experience on his blog. He shared some interesting thought about the shift of paradigm for an experienced Flash developer approaching HTML5. He also described some of the unexpected (but pleasant) surprises and challenges he faced during the development phase.
In this post, I’d like to complement his comments with further lessons we’ve learned along the path.
Let’s start talking about Performance. We heard many times that IE9 is fast…but I think this is the first time IE9 has been defined “smoking fast”! Grant, you are a primer!
I like to think about this game as a real world performance benchmark. No secret functions, no ray-tracing algorithms, no synthetic tests. Just a well designed HTML5 game.
The best way to test the performance, it’s to try it on your machine with your browser. For those of you that don’t have 7+ browsers installed on your machine , I recorded a video where I play the game on all the latest browsers builds available today.
Based on my personal experience, I reported the results in the table below. The CPU and Memory (Private Working Set) values have been captured using the Windows Task Manager, after about 5 seconds playing in the easiest map. A better measurement should take count of an average over time, since the complexity of the app increase quickly during the game.
Firefox 3.6
According to Grant’s tests, the game runs on Mac and iOS as well, although with poor performance. I don’t have any of these machines, can any reader record and share a video of the game playing on a Mac or an iPad (or any other device with HTML5 support)?
As we stated many times, HTML5 is doing great progress and this game is yet another confirmation of its capabilities. Let’s see a few key learning we made along the path.
I hope Grant will share more insights into his development experience with the Canvas. It’s exciting what he achieved in terms of complexity – it almost doesn’t feel any more like HTML!
A common technique that he used for the game is called “sprite animation”: you build a large image (composed of many sub images) and then you draw only one part of it at a given time. If you need to do an animation, you can re-draw very quickly on the same location – and all the human eye will see is an animation!
For example, here’s the Kraken sprite:
If you are interested to build your own animation, have a look at the library used in the game and gently made available by Grant: http://easeljs.com/
One of the interesting questions we got was: what is the max size of an image that the <canvas> can handle? 1000, 2000, 4000 pixels width? More? In this case I think the answer is “it depends”. It depends probably on the browser architecture (not necessarily the browser itself!) as there is a tight dependency on the video card and the OS. Having a browser that supports hardware acceleration helps, but there are still technical boundaries that can’t be overcome. Based on empirical experiments, we found that ~4000pixels seems to be the current limit. I’d be interested to hear from other people what they experienced on their machines.
There are hundreds thousands of fonts available today. For this project, Grant’s team got one that fits perfectly within the game. During the development of the project, they started with the ttf (True Type Font) format. They realized however that it was better to use an open font format…and what better one than WOFF, the new standard submitted by Mozilla and Microsoft to W3C!
We used Font Squirrel to generate the @font-face Kit (also check out TypeKit) and we initially generated the compact CSS rule:
@font-face {
font-family: freeBooterFont;
src: url('../fonts/freebooterupdated.woff') format('woff');
font-weight: normal;
font-style: normal;
}
It was working fine on all browsers, except Safari (both on Windows and Mac). Looking at online resources, we realized that – although committed to – they don’t support WOFF yet in the current build. Interesting, since it’s already on Chrome WebKit (shouldn’t the code be the same/shared?). I’m sure it will be added soon…and in the meantime we adjusted the CSS syntax (luckily super easy to do!) to include a fallback format.
src: url('../fonts/freebooterupdated.woff') format('woff'), url("../fonts/FreebooterUpdated.ttf") format("truetype");
Having one <audio> element on a page playing without plugins is already awesome.
Having 40 of them playing together is an orchestra! There are topics however that the spec doesn’t cover (and I think shouldn’t cover) where IMHO a little more documentation should be given by browser vendors to developers (IE Team already took note). For example, what’s the best practice to pre-load multiple audio samples? How many audio files can the browser play at the same time? I’m planning to spend some time on this topic in a future post.
I didn’t build the game, but I was reviewing the interim builds using the Developer Tools in IE9. They come with the browser, no need to install anything special, you just press F12 and they fire up.
The feature that I love more than anything else and that was very helpful in this project is “Format JavaScript”. If you go to the site now, you’ll notice that the code has been compressed and crunched to optimize performance.
If you are like me and you want to understand what’s behind it…you can go to the Script tab and press the “Format JavaScript” button. The result? A nicely “reverse-engineered” formatted source code! (Sorry Grant, I hope you won’t be upset )
There is much more to cover and to explore, but I’ll let you play the game now.
Resources:
Enjoy!
Coming on Friday Dec 17 10AM PST…
http://www.pirateslovedaisies.com/
Based on an interesting script of David Flanagan to generate a snow flake using recursive JavaScript and HTML5 <canvas>, I had some fun during the weekend and added some snow to my blog.
David’s implementation is based on several canvas elements (one for each snow flake) added to the DOM tree and animated using CSS. I preferred to create only one Canvas and animate all the flakes inside it. To avoid the Canvas to block the interaction with the page I added it as an element with background priority.
(function () {
// Start Animation only if browser support <canvas>
if (document.createElement('canvas').getContext) {
if (document.readyState === 'complete')
Snow();
else
window.addEventListener('DOMContentLoaded', Snow, false);
var deg = Math.PI / 180; // For converting degrees to radians
var sqrt3_2 = Math.sqrt(3) / 2; // Height of an equilateral triangle
var flakes = []; // Things that are dropping
var scrollspeed = 64; // How often we animate things
var snowspeed = 500; // How often we add a new snowflake
var maxflakes = 20; // Max number of flakes to be added at the same time
var rand = function (n) { return Math.floor(n * Math.random()); }
var canvas, sky;
var snowingTimer;
var invalidateMeasure = false;
function Snow() {
canvas = document.createElement('canvas');
canvas.style.position = 'fixed';
canvas.style.top = '0px';
canvas.style.left = '0px';
canvas.style.zIndex = '0';
document.body.insertBefore(canvas, document.body.firstChild);
sky = canvas.getContext('2d');
ResetCanvas();
snowingTimer = setInterval(createSnowflake, snowspeed);
setInterval(moveSnowflakes, scrollspeed);
window.addEventListener('resize', ResetCanvas, false);
function ResetCanvas() {
invalidateMeasure = true;
canvas.width = document.body.offsetWidth;
canvas.height = window.innerHeight;
sky.strokeStyle = '#0066CC';
sky.fillStyle = 'white';
function drawFlake(x, y, size, order) {
sky.save();
sky.translate(x, y);
snowflake(order, 0, Math.floor(sqrt3_2 * y), size);
sky.fill();
sky.stroke();
sky.restore();
function snowflake(n, x, y, len) {
sky.save(); // Save current transformation
sky.beginPath();
sky.translate(x, y); // Translate to starting point
sky.moveTo(0, 0); // Begin a new subpath there
leg(n); // Draw the first leg of the fractal
sky.rotate(-120 * deg); // Rotate 120 degrees anticlockwise
leg(n); // Draw the second leg
sky.rotate(-120 * deg); // Rotate again.
leg(n); // Draw the final leg
sky.closePath(); // Close the subpath
sky.restore(); // Restore original transformation
// Draw a single leg of a level-n Koch snowflake.
// This function leaves the current point at the end of
// the leg it has drawn and translates the coordinate
// system so the current point is (0,0). This means you
// can easily call rotate() after drawing a leg.
function leg(n) {
sky.save(); // Save current transform
if (n == 0) { // Non-recursive case:
sky.lineTo(len, 0); // Just a horizontal line
else { // Recursive case: _ _
// draw 4 sub-legs like: \/
sky.scale(1 / 3, 1 / 3); // Sub-legs are 1/3rd size
leg(n - 1); // Draw the first sub-leg
sky.rotate(60 * deg); // Turn 60 degrees clockwise
leg(n - 1); // Draw the second sub-leg
sky.rotate(-120 * deg); // Rotate 120 degrees back
leg(n - 1); // Third sub-leg
sky.rotate(60 * deg); // Back to original heading
leg(n - 1); // Final sub-leg
sky.restore(); // Restore the transform
sky.translate(len, 0); // Translate to end of leg
function createSnowflake() {
var order = 2;
var size = 10 + rand(90);
var t = (document.body.offsetWidth - 964) / 2;
var x = (rand(2) == 0) ? rand(t) : t + 964 + rand(t); // Make it fit with my blog
var y = window.pageYOffset;
flakes.push({ x: x, y: y, vx: 0, vy: 3 + rand(3), size: size, order: order });
if (flakes.length > maxflakes) clearInterval(snowingTimer);
function moveSnowflakes() {
sky.clearRect(0, 0, canvas.width, canvas.height);
var maxy = canvas.height;
for (var i = 0; i < flakes.length; i++) {
var flake = flakes[i];
flake.y += flake.vy;
flake.x += flake.vx;
if (flake.y > maxy) flake.y = 0;
if (invalidateMeasure) {
var t = (canvas.width - 964) / 2;
flake.x = (rand(2) == 0) ? rand(t) : t + 964 + rand(t);
drawFlake(flake.x, flake.y, flake.size, flake.order);
// Sometimes change the sideways velocity
if (rand(4) == 1) flake.vx += (rand(11) - 5) / 10;
if (flake.vx > 2) flake.vx = 2;
if (flake.vx < -2) flake.vx = -2;
if (invalidateMeasure) invalidateMeasure = false;
} ());
I did a quick test check with performance, and it looks like IE9 perform better with CPU/Memory usage (IE9 Beta Refresh, Firefox 4 Beta7, Chrome 10 Canary, Opera 11Beta and Safari 5).
You can find the original script from David here, the script with my updates here.
Happy Holidays!
“HTML5” is huge. Different specifications are at different status: First Public Working Draft, Working Draft, Candidate Recommendation, Proposed Recommendation, and lastly Recommendation.
As we stated many times before, it’s important to make it right.
Browser makers have a big responsibility with developers: it’s wrong to claim “standard support” for a specification that is still changing. Or, at least, they should make clear it is still a work in progress (for example, by using vendor prefix extensions).
Rushing the implementation of a specific feature and call it “done deal” is dangerous and in some circumstance can bring to unpleasant results. Today we’ve seen an example of this with an important specification: Web Sockets.
Web Sockets enable Web applications to maintain bidirectional communications with server-side processes. The specification (from the WebApps WG) is currently in the “Working Draft” stage; there is also a dependency on the Web Sockets Protocol, discussed in the IETF hybi mailing list.
Chrome 4+ has been the first to implement it in a “final RTW build” at the end of 2009, followed by Safari 5.0.2. Instead Firefox was planning to support them in the version 4beta and Opera in version 11, but they never went in production (credits for monitoring its status before going live). IE doesn’t implement this spec in current build.
Over time the specification changed and each browser tried to adapt by releasing sequential implementation updates. Some developer even built “The Ultimate HTML5 Browser Support Test” based on specs like this that are still in Working Draft stage. A few users commented on IE Blog about IE being “the only browser” not supporting it yet.
On Nov 26th, Adam Barth shared the results of his experiments with IETF.
“The Upgrade-based handshake is vulnerable to attack in network configurations involving transparent (or intercepting) proxies.”
In other words, the current protocol used by Web Sockets might be unsecure and unstable.
Based on this discovery, Firefox and Opera promptly did the right thing, announcing they would disable Web Sockets in future releases – until a solution is found. At the time I’m writing this post, I haven’t seen any announcement made by Google or Apple yet, although I’m confident they will follow soon (as Mozilla developers do).
In developer language, this is what I call a “breaking change”. Changing (or even worst, removing) support for a feature from a version to the next one – possibly breaking all applications built by developers based on the assumption that the feature (a supposedly “web standard”) would have been supported moving forward.
After today announcements, I wonder how many apps using Web Sockets will need to be brought offline, or “freezed” or “re-written” – at least until the spec get more solid and secure? Did these developers use browser feature detection, or they just assumed that any version after (for example) Chrome 4 will support that feature the same way?
What about those existing enterprise solutions, for example Kaazing, that rely on the assumption that “most browsers” already support Web Sockets? Will they just use the Flash-based layer (as they do for IE today) for any browser?
Personally I like Web Sockets. I’m looking forward to seeing them available in all browsers. But I also do care about consistent implementations, that work the same (interoperable, secure, stable) way across any browser – over time. I don’t want to write some code today, falling in the “(non) Web Standard trap”, and then have to re-write my code in 1 year from now because that particular implementation wasn’t exactly ready for prime time yet and has been removed or changed.
I don’t want to see the “IE6 phenomenon” happening again to “Chrome4” or others. Do you?
Someone today was so disappointed that he created a short humoristic video…
Time to go back to W3C and IETF to discuss what went wrong and look at what we can do to accelerate the progress of this (and other) specs...