Monday, February 23, 2015

Shine Get! Resolution Independence

New Screen Scaling Methods in melonJS v2.1.0

The upcoming v2.1.0 release contains a new set of screen scaling methods that will help you ship your game on multiple platforms. This feature was contributed by Jihed Kallel, kudos for that!

Rationale

Many years ago, all computer and television screens used roughly the same screen resolution and aspect ratio. Things were easier then. Today, we have innumerable screen resolutions in innumerable aspect ratios across computers, TVs, mobile devices, ... How do you get your game to "look right" on an iPad 3 (2048×1536, 4:3), a Surface Pro 3 (2160x1440, 3:2), a OnePlus One Android phone or 1080p TV (1920x1080, 16:9), and a 15' Mac Book Pro Retina (2880×1800, 8:5 aka 16:10)?

This short list covers the most common aspect ratios that you will come across if you ship your game on multiple platforms; 4:3, 3:2, 16:9, and 16:10. Getting a game to look right on screens that use each of these aspect ratio can be challenging. Luckily, melonJS now has some new scaling modes to help you get the most out of those screens just by setting an option!

All About Aspect Ratios

Aspect ratio is the term given to the ratio between the width of an image and its height. To compute the aspect ratio of an image, find the greatest common devisor between its width and height, and divide the width and height by the GCD:

gcd(1280, 720) = 80
1280 / 80 = 16
720 / 80 = 9

This illustrates how the aspect ratio of 1280x720 is 16:9.

The usefulness of the aspect ratio derives from its ability to ease comparisons. For instance, knowing that 1280x720 and 1920x1080 have the same aspect ratio (16:9) means that an image designed for either resolution will fit correctly into the other without letter boxing (black bars on the top or sides of the image) and without stretching or squashing the image. The image will also fit appropriately on any other 16:9 display after scaling.

Scaling to Different Aspect Ratios

But what if you wanted to display a 4:3 image on a 16:9 display? Or on any non-4:3 display, for that matter? Well, you have a couple of options! Each option should be considered carefully, as it will affect how your game is designed.

First, let's take a look at a game designed for a 4:3 display; the good old Platformer Example that ships with melonJS since forever.

Platformer Example, Design Resolution (click to enlarge)

This is the Platformer Example shown in its "design resolution" (800x600, 4:3). The design resolution is the term we've given to the width and height numbers the you pass to the me.video.init() method when initializing the display. Here's what the game looks like on a 1280x720 (simulated) display with no scaling:

Platformer Example, No Scaling (click to enlarge)

Right away you notice the black area around the image. And the second thing you may notice is that there is significantly more black space on the sides than on the top and bottom, because a 16:9 display is a wider aspect ratio than a 4:3 display. You can ignore the Chrome address bar and title bar; the perimeter of the black area is exactly 1280x720, which is what you would see on something like a mobile device.

melonJS has supported automatic screen scaling since I joined the team two years ago with the release of v0.9.5. And it has also provided a method of maintaining the aspect ratio when auto scale is enabled.

The image shown below is what the Platformer Example looks like with auto scaling enabled, while maintaining the aspect ratio. It shows how melonJS performs a "letterbox" scale; the image is scaled to fit the display without distortion. This has the effect of leaving black bars on the sides, but not above or below the image:

Platformer Example, Auto-scaling, Maintain Aspect Ratio (click to enlarge)

The second image does the same scaling, but this time ignoring the aspect ratio, and stretching the image to fill out the entire display area. This causes the image to look squished vertically. It is most noticeable in the spaceman's helmet, which is supposed to be a perfect circle!

Platformer Example, Auto-scaling, Ignore Aspect Ratio (click to enlarge)

As of melonJS v2.0.x, that's where your scaling options end. But with v2.1.0 we have some new things to show you! The letterbox mode above is now called fit, and the stretching mode is simply called stretch. The other new modes may take some getting used to, but some example use cases and images may help illustrate what they do, and when you should use each of them.

fill-max

The first new scaling mode is called fill-max, and it is ideal for games that take place on a single, non-scrolling screen. It uses your design resolution as an indication of the maximum dimensions which your screen will use. In the case of the Platformer Example, that means the width will be 800 or less, and the height will be 600 or less. When scaling to a wider aspect ratio, the width will be locked to 800, and the height will shrink. Or when scaling to a taller aspect ratio, the height will be 600 and the width will shrink.

A well-designed game screen can use this scaling mode to its fullest advantage by keeping "critical to see" elements within the center of the design resolution frame. The sides and top/bottom will be trimmed accordingly to fit the display. That also means you can have "nice to have" elements on the outside edges of the design resolution, keeping in mind that they may not be visible on all displays.

Here's what it looks like on the Platformer Example. It's not an ideal use case, but you can tell that the width is still the same; you can see the plant on the left and the spikes on the right. The height has shrunk though; you see less of the sky. Bonus: the spaceman's helmet is still a circle!

Platformer Example, fill-max (click to enlarge)

flex-height

I didn't get a screenshot of this mode, because it looks identical to fill-max. The difference with this scaling mode is that the width will always stay constant (e.g. 800), and the height will grow or shrink to fit the display. Scaling to a 16:9 display, the 4:3 image looks identical to the one above. Scaling to a reeeally tall display with this mode would let you see way up into the sky by making the height greater than 600, while the fill-max mode would just lock the height to 600 and shrink the width.

This mode is primarily useful for vertical shooters, where you want to see more of the level vertically; having a taller display is more advantageous and provides a better experience.

flex-width

Along the same lines, flex-width keeps a constant height (600) and grows or shrinks the width to fit the display. In this case, you get to see more of the level, while you still see the same amount of sky as the original image.

This is my personal favorite, because it works well with pretty much any scrolling level in a widescreen format. With all of these new modes, you will want to be careful about how HUD elements are positioned on the screen. The Platformer Example places the score counter (bottom right, number 0) by querying the viewport dimensions. It is probably better to query the renderer dimensions instead, because the viewport can technically be sized differently than the screen.

Platformer Example, flex-width (click to enlarge)

What's Next

We already have plans to implement two more scaling modes for the following melonJS release (after v2.1.x); fill-min (complementary to fill-max) will use the design resolution as the minimum dimensions, and they can only grow. And flex will be like a combination of flex-width and flex-height, allowing both dimensions to grow or shrink while maintaining the original scale.

That last bit is important; it means that you can scale the image by 2x or 3x, and then flex it to fill the entire display without messing up your beautiful pixel art! This is nice because all of the currently supported modes require auto-scaling, and do not work at all with integer scaling. The bad thing about this mode with integer scaling is that you will see the full map on high resolution displays like 4K monitors but everything will be too tiny to make out the detail.

Wrapping Up

We've seen how we can define a "design resolution" and let melonJS figure out the hard parts of making it fit onto displays of different shapes and sizes. The only points to consider are what design resolution to choose, and which scaling mode to use.

The resolution choice is usually pretty easy, and depends on things like your tile and/or sprite sizes. It's easy to pick something pretty "standard" like 32x32 tiles on a 800x600 screen. Choosing the scaling mode is a bit more challenging, and depends more on the kind of game you are developing. Being able to answer some simple questions might help you make the right decision:

  • Q: Do you want to show your design resolution only, nothing more and nothing less?
    A: Use fit or stretch modes.
  • Q: Do you want to show approximately the design resolution only, but you're ok with hiding the edges?
    A: Use the fill-max mode.
  • Q: Do you want to show more of the map vertically on tall screens/portrait orientation devices?
    A: Use the flex-height mode.
  • Q: Do you want to show more of the map horizontally on wide screens/landscape orientation devices?
    A: Use the flex-width mode.

With some clever use of entity placement and your chosen scaling mode, you can build games in a resolution-independent manner. It doesn't have to be rocket surgery! One more thing that may help while designing maps and screens is an aspect ratio template like this one from v-play.

1 comment:

  1. This comment has been removed by a blog administrator.

    ReplyDelete