Viewports and the scaling & positioning of the canvas

Having just spent a couple of hours poring over my code for scaling & positioning the game canvas I thought I’d share the experience.

Despite having made several HTML5 games over the last couple of years I’ve never experienced any real issues with scaling or positioning. Just recently however I’ve started to notice inconsistencies between browsers and OS versions.

In some cases the canvas would scale perfectly but not position properly. In other cases the canvas would not scale properly yet position well in the centre of the screen.

My criteria is simple – I want the games to be played in any orientation and to fit consistently in the centre of the screen.
Where full screen is available in either width (landscape orientation games) or height (portrait) I will also aim for that.

It’s 90% there. On Galaxy S2+ and iPhone4+ portrait games load and scale to fit fine in portrait and landscape games load fine and scale in landscape.
The problem arises on iOS when the games are loaded in the wrong orientation.

e.g. if the game is a portrait game and it loads up in portrait mode then it scales to fit just fine, as you would expect. (illustration 1)

ipadscale-portraitload

illustration 1 – game loads in portrait mode

But if the player then decides to play it in landscape it scales fine but doesn’t centre correctly. Just sits to the left of centre. (illustration 2)

illustration 2 - game loaded in portrait and rotated to landscape

illustration 2 – game loaded in portrait and rotated to landscape – note white space where HTML BODY is simply untouched.

If the game loads up in landscape then it centers beautifully and is playable (illustration 3)

iPad HTML5

illustration 3 – game loaded whilst tablet in landscape mode

If the player then rotates to portrait the game scales just fine on iPhone to fit the screen but on iPad it sits rather awkwardly to the right of the screen with about a third of the content out of sight. (illustration 4)

ipadscale-landscapeload-rotate

illustration 4 – loaded in landscape and rotated to portrait

This has an impact on my regional touchEvent code that I use for driving menus and other in-game features.

I no longer present a banner message instructing the player to rotate. It seems clunky and my clients hate it.

Of course it’s far too easy to suggest that as long as the games load up in their preferred orientation then surely I’ll be OK.
The reality is it needs to work and work properly.

So what’s my code doing?

Well surprisingly little.
I certainly don’t sniff the device. It’s 2013. So I don’t actually know I’m dealing with iOS any more than I know it’s a desktop or mobile browser.

The first thing I do is record the screen’s physical width less any scrollbars.

var w = window.innerWidth;
var h = window.innerHeight;

I also record the game’s intended canvas dimensions.

var rw =320;
var rh = 480;

Then to accurately scale the canvas I use the Math.min() method to first determine how best to scale the canvas to preserve the aspect ratio.

multiplier = Math.min((h / rh), (w / rw));
var actualCanvasWidth = rw * multiplier;
var actualCanvasHeight = rh * multiplier;

Fairly simple stuff there just to keep the aspect ratio of the game’s canvas intact and as previously stipulated aim for full screen in the preferred orientation.

To actually position the canvas on the screen I use CSS.

So first I’ve already got a handle on the canvas and stored it within my global namespace (g):

HTML

<body>
<div id="game">
<canvas class="canvas"></canvas>
</div>
</body>

JavaScript

g.gamecanvas = document.getElementById('game');

CSS

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

I then make sure that the canvas parent is displaying as a block level element, assign a width to it and use the margin property to centre it. The canvas fits its parent to the pixel.

g.gamecanvas.style.display = "block";
g.gamecanvas.style.width = actualCanvasWidth + "px";
g.gamecanvas.style.height = actualCanvasHeight + "px";
g.gamecanvas.style.margin = "0px auto 0px auto";

My scaling function is called every time the onresize() or onorientationchange() events are fired. It is also called at the very top of the code before the assets are loaded.

So when we tie all this together we get a perfect presentation the first time the game is loaded.
The canvas scales to fit the orientation and centers perfectly in the centre of the screen.

So why on earth do I get that “dead” area when the tablet is rotated from portrait to landscape? (illustration 2 above)
Why does the canvas shift over to the right when the tablet is rotated from landscape to portrait? (illustration 4 above)
There has to be something else to look at.

The answer lay in the META description for the VIEWPORT.

Here’s what I used to present in the index.html of every game:

<meta name="viewport" content="width=320, height=440, user-scalable=no, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" />

What a mess. I’d never touched this in almost two years.
Understanding the viewport is essential to understanding mobile presentation. Generally speaking the rule is if you don’t understand how it works don’t use it. The results can be undesirable.

Rather than go to the trouble of fully understanding what the viewport definitions are and do I decided to look at what I actually want to achieve. The answer here is very simple – a) stretch content to screen dimensions, b) suppress ability to pinch / zoom.

There is no need to specify the physical width or height in pixels of the device screen. There is also no need to restrict user scaling with the user-scalable attribute. Finally there is no need to set a maximum-scale, which I must admit I found surprising.

The only code I need here is:

<meta name="viewport" content="width=device-width, initial-scale=1">

The scaling of the canvas and positioning of the parent element within the document body are now perfect. No more “dead” regions and no more strange horizontal positioning.

There appears to be more sympathy for my lousy meta code within Android browsers than there is with iOS and Safari.

Creating a very basic “Star Wars” style trench scene viewed from above

Prototyping new game ideas is a lot of fun. Thanks to some serious enhancements to my game framework it’s also pretty quick.

I enjoy day-dreaming new ideas and recently I was watching something on TV that put me in mind of the film Star Wars. It was a fast paced scene in a TV show with cars, helicopters and trucks all viewed from above. I was struck by how effective the shot was in portraying speed with the buildings that lined the streets.

The Star Wars link came with the closing scenes in the first film as the fighters raced down the trench. It occured to me that this scene viewed from above might work quite well for a (yet another !) shooting game. I quite liked the claustrophobia of it, amongst other things.

So I had a bash at putting together a short test.
The theory was quite simple. I would use a simple fillRect() to block out the 5 key areas of the trench. The lighter shades at the edge would of course be the top of the trench (the surface if you will of the Death Star) and the darker shades would represent the inner walls and the trench’s floor.
You can see the rectangular divisions highlighted in the image below.

All I needed to do was give the impression of movement.
For this I am using lines and paths.

I created a trenchLine() class and initiated it with 2 y values. Nothing more is needed at the moment.
The first y co-ordinate is for the first 2 points and the last 2 points (see the image below).
the second co-ordinate is for the middle points which will be moving that bit slower.

As the game begins I initiate around 4 trench lines to be drawn over the grey background at equidistant intervals down the screen. (In the illustration below the 4th line is just off the top of the canvas so not yet visible)
The key here is in how the trench line breaks up to represent the pseudo-3D nature of the trench.

click to enlarge

So I originate the line at x = 0.
The line itself is broken in to 5 shorter lines.
On a 320 wide play area I draw the line’s points at

1. x=0, // left hand side
2. x=32, // edge of trench – about to drop to floor
3. x=48, // left hand side of the 192px wide floor
4. x=240, // right hand side of trench floor
5. x=288 // edge of trench on right hand side

Each point moves at a set speed down the screen.
I set the trench speed to be, say, 4 pixels per frame.
This affects points 1,2, 5 and 6.

But the trench floor needs to move slower and for these points (3 and 4) I move them at 3 pixels per frame.

I have a simple line drawing routine:


function drawLine(x1,y1,x2,y2,c)
{
 try
 {
 g.ctx.beginPath();
 g.ctx.moveTo(x1, y1);
 g.ctx.lineTo(x2, y2);
 g.ctx.closePath();
 g.ctx.strokeStyle = c;
 g.ctx.stroke();
 }
 catch (e)
 {
 write("drawline: " + e.message);
 }
};

Quite straight forward in that it accepts the start and end points of the line and the colour that you wish to draw it.
I then pass the following 5 draw instructions to it to create my trench line:


drawLine(0,o.y,32,o.y,"#aaaaaa");
drawLine(32,o.y,64,o.y2,"#666666");
drawLine(64,o.y2,256,o.y2,"#444444");
drawLine(256,o.y2,288,o.y,"#666666");
drawLine(288,o.y,g.canvaswidth,o.y,"#aaaaaa");

The overall effect here is that the outer lines effectively overtake the inner lines and you get the impression of a moving trench from above – as per the screen shot.

I created some placeholder sprites and threw them around to see how effective playing the game in a narrow 192px corridor might be. So far so good.

Watch this space :)

HTML5 Canvas Text Wrap Tutorial

I’m using a small amount of dialogue in Distant Orbit to help tell the story. One thing that I wanted to maintain was simplicity when it comes to handing over the text files for localisation.

So to this end I needed to allow for text to wrap across lines within the canvas.
Some languages are quite a bit longer and more complex than English. German and French in particular can have some lengthy translations. So I wanted to be able to give the translator freedom when it comes to choosing the appropriate words to tell the story.

There isn’t a native textBlock functionality within the Canvas text API so I looked around for a solution and found this one pretty much straight away.
The key here is in understanding what context.measureText() is doing for you. I think it’s pretty self explanatory.

1.8.7 HTML5 Canvas Text Wrap Tutorial.

The pain of fixing canvas dimensions across all devices

In an ideal world I would develop all of my games at a fixed resolution. That resolution just now would be something like 320 x 460. Largely because according to my analytics data from Google the majority of people playing the games do so on an Apple device such as iPhone or iPod Touch.

Of course the problem here is that for every 10 Apple players there is going to be at least 1 Android player equipped with an HTC Desire or possibly a Galaxy Tablet or… the list is endless.

Fixing the canvas size is also great for knowing where items can initialise on screen. You are able to set an accurate scale to your games, you know just how big the sprites are and just how much freedom of movement they can have.
With more fluid dimensions you run in to the territory of working with relative positioning – which is no bad thing and certainly not a difficult thing to work with if you are able to consider it from the outset.

A quick glance at my analytics title sends a shiver down my spine:

48,552 visits used 730 screen resolutions + operating systems

730 different screen resolutions !

So I delve a little deeper to see just what has been viewing these games.

320×480 iPhone
320×480 iPod
768×1024 iPad
320×401 Android
320×480 Android
320×452 Android
320×396 iPod
320×396 iPhone
319×50 Android
320×341 Android
240×320 Android
320×399 Android
320×345 Android
800×480 Android
480×320 Android
480×241 Android
320×240 Android
320×488 Android
267×309 Android
427×235 Android
480×800 Android
1280×1024 Windows
533×239 Android

So at this point I start to wonder about a few things. What exactly are my options ?

  1. Just fix the canvas width and height to the available width and height of the browser window
  2. Ignore it and continue to force dimensions to 320 x 460
  3. Take the window dimensions and try to calculate some kind of a scalable dimension based on height / width ratio
  4. Take the window dimensions and offer canvas dimensions based on a number of presets. e.g. 320 x 460, 480, 690, 640 x 920 – possibly centering the content with CSS

Considering each of these options of course introduces other questions.
For starters at the point when you grab the window dimensions how has the player got the handset held ? Landscape or Portrait ?

For example, if the screen dimensions come back as 640 x 480 is that just because the handset is on its side ? Or is it a desktop resolution.
We can of course check window.orientation for this.
Generally a value of 0 (zero) indicates a default orientation of portrait.
90 and -90 indicate the alternative orientation of landscape.
However, I notice that the Samsung Galaxy Tablet 10.1 sets its default to 0 in a landscape orientation. Very useful indeed. Not.

If, in the case of option 4, I decide to continue fixing dimensions do I need to offer multiple sized sprite sets to avoid sprite scaling ? Most probably. Scaling is a performance dog just now and can yield unwanted visual effects.

So how do you solve a problem like this ?
What exactly can you do to give the player a good full screen experience AND ensure that you are catering for all devices ?
Does anyone have their own experiences / solutions that they might wish to share ?

I ask this question deliberately since I have coded a number of solutions, none that I am truly happy with. Please let me know your thoughts. If comments close (after 7 days to restrict spammers) please mail me. You can find the address in the about page. I will publish your comments if you so wish.

 

Area 51 – development update

Area 51Decisions, decisions.
Having broken the back of the arcade element to Area51 I’m now left standing at a crossroads in terms of game design.
There are numerous elements to the game that I am enjoying – the cramped gloomy corridor feel, shooting monsters – spawning blood, shooting toxic barrels and fireballs, powering up your weapon base, muzzle-flashes, watching the discarded bullet shells thrashing away from your current weapon with every shot.. all adding up to a quite enjoyable experience in terms of fun and player feedback.
But still the game seems incomplete. It’s just not enough to work your way through endless hordes of monsters. Players need more intricate challenges.(I just can’t seem to make a 2p game where all the action is over within 10 seconds and the rest is just repeat ad-infinitum)

So I’m looking at ways to boost what I call the “immediate action”. i.e. the challenges that are thrown at the player mid-level. Which essentially boils down to having more to shoot, more to dodge, managing health and managing ammunition. The base weapon – the pistol – will have infinite ammo so you can always fall back to that. But the better weapons will sap ammunition at a good rate.

But there is another element to the game that I want to improve and that is what I call the “deep action”. This is essentially the true goal of the game.
I had always intended for Area51 to be a game about survival and ultimately escape. I had toyed with the idea of rescuing the good guys but am now not sure it is necessary.
I want the player to move along a fairly linear path blasting his way out of situations whilst facing increasingly more menacing adversaries. I also want the player to have some sway on the direction that the action takes.
At the end of every battle I currently reset the level and present a ticker text display of the next area. e.g. Entering Bio-Mechanical Research Center
I hard code these scenarios.
What I really want is to provide a loose map that is generated randomly at the start of every game. As the player progresses he is given choices as to which direction (basically left, right or straight ahead – never back) he wishes to take. Where I take the player away from the central “core” path I ultimately ensure that I bring them back. So regardless of the areas that he ends up in he can be sure that at all times he is actually moving forward.

A likely scenario might be:

BIO-MECHANICAL RESEARCH CENTER completed.
You can now enter WEAPONS LABORATORY, CENTRAL LABORATORY COMPLEX, OXCART RESEARCH.

These decisions are exclusive. You cannot for example enter the weapons lab and then come out and go in to oxcart research.
Each location will be weighted. That is, depending on the location there will be an increased chance of receiving health, ammo or bigger monsters ! The title of the location will give the necessary clues.

One of my favourite games as a kid was The Eidolon. An Atari 800XL masterpiece of tension and graphical loveliness. The highlight was of course the dragon that you always knew you were close to because the screen glowed and the noise level increased.
I am looking at implemented something similar for Area51. A simple radar implementation wouldn’t be hard.

I deliberately took a couple of weeks off developing this game so that I could just sit back and play it.
I’ve found it useful. Very useful. It allows me to see the game through a gamers’ eyes as opposed to a developers’.
I feel like I can now finally move forward and complete the game to the original vision.

As always I aim to complete the game code such that I can concentrate on playing with the graphics. I need to get these sketchbook illustrations and convert them in to pixels !

Watch this space !