Phaser and multi-resolutions textures issue

Raise your hand , and comment on this issue if you find it important.

Hey guys, the last months have been very exciting! We’re still working hard on A Blind Legend, there should be some blog posts about it sooner or later (you know how indie games are going)… 🙂 Next to that, we’re working on some HTML5 games. We already made several using pixi.js (with pure JavaScript and TypeScript) and others with Flambe (based on Haxe).

That time we wanted to test an other popular framework: the famous Phaser game engine. We had a long debate internally to choose the language we’d pick up between pure JS, Haxe and TypeScript and we finally opted for Haxe. There were already some Haxe externs for Phaser so that was great.

As Citrus Engine‘s developers, we were confident that Phaser comes with many good options on top of PixiJS (it is build with it) that we’d appreciate: state management, physics, multi-resolution support, sounds… However our journey isn’t as shiny as we hoped (we’re looking at you multi-resolution support)!

All started goods, “Phaser has a built-in Scale Manager which allows you to scale your game to fit any size screen. Control aspect ratios, minimum and maximum scales and full-screen support”. There isn’t any official example concerning the Scale Manager but Photonstorm (Phaser’s creator) made a book. Inside, there are a ton of examples, informations on what browsers support, and the different scale offered by Phaser:

  • NO_SCALE: The Game display area will not be scaled – even if it is too large for the canvas/screen. This mode ignores any applied scaling factor and displays the canvas at the Game size.
  • EXACT_FIT: The Game display area will be stretched to fill the entire size of the canvas’s parent element and/or screen. Proportions are not mainted.
  • SHOW_ALL: Show the entire game display area while maintaining the original aspect ratio.
  • RESIZE: The dimensions of the game display area are changed to match the size of the parent container. That is, this mode changes the Game size to match the display size. Any manually set Game size is ignored while in effect.
  • USER_SCALE: The game Display is scaled according to the user-specified scale set by setUserScale. This scale can be adjusted in the resize callback for flexible custom-sizing needs.

The most obvious for us, was to use the RESIZE scale mode. It works as expected, at this moment everything was going well, but one question remained : how does Phaser manage multiple texture resolutions? Simple answer, it doesn’t, and in our humble opinion that’s a big problem.

We saw tons of examples explaining how to make a multi-resolutions game, but they weren’t right. Sorry guys, but nowadays you can’t make a game with only one size of assets! From a 2560 x 1600 (resolution of a Nexus 10) to a 960 x 640 (iPhone 4S) you shouldn’t use the same assets, otherwise they are blurry (too small on a Nexus 10) or they consume too many resources (if you load big assets made for the Nexus 10 on an iPhone 4S). You have to create multi-resolutions assets : @1x @2x @3x @4x.

Fortunately nothing prevents you, in Phaser, to load different assets based on let say your base game size. That’s in their management there is an issue. We’re big fan of Starling framework, and Daniel made something really useful to developers: all the multi-resolutions textures report the same size. Your @2x will have the same width & height than the @4x, Starling handles everything under the hood. That’s of course because the width/height calculation and uv setup take a ‘scale’ property into consideration. The big advantage is, you don’t bother to position elements depending on your scale factor (@1x @2x…), you can keep the same coordinates for every resolutions! This advantage becomes obvious when you’re thinking on a physics based game: you want your physics to work exactly the same no matter the device, the resolution etc. Having the same size reported for your multi-resolutions assets makes it easily to attach physics to them.

Of course, you might say, “I don’t care, my app is completely responsive”. Sure in that case, then multiplying your coordinates with an extra variable doesn’t make much difference, but in a “fixed world” where you move around with a camera, that matters. So for the game world that’s important, for ui, that’s less important I guess, still we could cut down on a lot of development time, even if you manage to create helper methods for placement.

Ok, so this isn’t managed by Phaser, but is it its fault? Phaser is built on top of Pixi, so perhaps Pixi doesn’t handle that neither? Yes, that was true. But that was in their dev branch for the V3 and now that V3 is live, it’s plenty supported! So, no issue, If I update Pixi would Phaser benefit from the latest update? Unfortunately not as Phaser manages Pixi in a certain way (bypassing some Pixi stuff), so it can’t be added easily. And here is the dilemma: should I use Phaser without this (extremely important and useful) feature or go to Pixi directly? We’re hoping this will be added very quickly…

Anyway, we made a template to manage multi-resolution – but not avoiding multiplying positions with a certain factor. This is an extra layer to phaser, which we can get rid of once it’ll use PIXI v3 completely:

Here’s the Github project link that we base the following code on.

First our Main class :

 
package;

import phasermultires.Root;
import phaser.geom.Rectangle;
import phaser.Phaser;
import Intro;

class Main extends Root
{
	
	static function main() { new Main(); }

	override function new()
	{
		super();
		//Our game is designed at scale 1, for an ipad in portrait mode
		base = new Rectangle(0, 0, 1536, 2048);
		
		//we have a lot of scales available as we are going to support even small android screens
		scales = [0.25, 0.375, 0.5, 0.75, 1];
		
		forceOrientation = Root.ORIENTATION_PORTRAIT;
		
		//using device pixel ratio, we can support retina and actually load up the 1x assets. but is not always necessary
		useDevicePixelRatio = false;
	}
	
	//This will setup the Phaser config object appropriately.
	override function setupConfig()
	{
		antialias = false;
		enableDebug = false;
	}
	
	override function initialize() 
	{
		super.initialize();
		game.time.desiredFps = 30;
		game.state.add('Intro', Intro, true);
	}
	
	override function onEnterIncorrectOrientation(){trace("onEnterIncorrectOrientation");}
	override function onLeaveIncorrectOrientation(){trace("onLeaveIncorrectOrientation");}
	
}

Now our Intro state which will place an image at the center of the stage, and one at the bottom right corner with some padding.

 
package;
import phasermultires.states.MultiResState;

class Intro extends MultiResState
{

	public function new() {
		super();
		
		letterbox = false;
		containerFitMode = MultiResState.FULLSCREEN;
		
		//the container (phaser group) will be centered on screen.
		containerAlign.setTo(0.5, 0.5);
    }

    override function preload():Void {
		
		super.preload();
		
		//we preload a texture atlas of the right scale variant (built with texture packer for example)
		load.atlasJSONHash('assets', 'assets/@' + root.scaleFactor + 'x/assets0.png', 'assets/@' + root.scaleFactor + 'x/assets0.json');
		
		//in the preload function, the container is ready, so we can display a loading bar before we get to the initialize part.
    }
	 
	 override function initialize() {
        super.initialize();
		
		//Now the state begins.
		
		var image = add.sprite(0, 0, 'assets', 'image.png', container);
		image.anchor.setTo(0.5,0.5);
		
		var bottomRightImage = add.sprite(0, 0, 'assets', 'image2.png', container);
		bottomRightImage.anchor.setTo(1,1);
		
	 }
	 
	 /**
	 * onResize is called once after initialize, and then every time the game resizes, 
	 * to get a responsive layout we set our real positions here, but you really don't have to, see Note 3.
	 **/
	 override function onResize()
	 {
		super.onResize();
		
		//We can use stageRect to place things relative to the stage no matter how container is setup:
		image.x = stageRect.centerX;
		image.y = stageRect.centerY;
		
		bottomRightImage.x = stageRect.right - 10 * root.scaleFactor;
		bottomRightImage.y = stageRect.bottom - 10 * root.scaleFactor;
	 }
	 
	override function shutdown():Void {
		//Here we can get remove signal listeners or clean things up obviously.
		super.shutdown();
	}
	
}

Note : since the container is centered, we can just keep image at 0,0 instead of placing it at stageRect.centerX/Y on resize. You could however keep the container at 0,0 on screen with containerAlign though.

Note 2 : root.scaleFactor is what you’re going to want to multiply positions or dimensions with. For example if I were going to place an image2 right beside an image1, I would set image2.x = image1.x + image1.width*root.scaleFactor .

Note 3 : If you’re not going for a responsive game, that you know for sure it’s not going to resize : mobile for example, then you don’t need to position objects in onResize. As I’ve said here, the container is aligned at the center , to an image at 0,0 will already be centered, if the game is never going to resize later, you don’t need the code in onResize and just use :

var image = add.sprite(0, 0, 'assets', 'image.png', container);
image.anchor.setTo(0.5,0.5);
		
var bottomRightImage = add.sprite(stageRect.right - 10 * root.scaleFactor, stageRect.bottom - 10 * root.scaleFactor, 'assets', 'image2.png', container);
bottomRightImage.anchor.setTo(1,1);

Big Drawback though : when using this system, physics doesn’t work correctly. The positions are correct even though we’re going through many transformed groups, the bodies’ dimensions are incorrect for some reason – we don’t currently know why. I mean input and checking bounds with getBounds() give correct results, yet enabling bodies doesn’t work, even when enabling bodies after the groups have been scaled (which was the intuitive thing to do when we saw body scales were wrong). Of course if this was a transform problem related to the parent groups, the positions would be wrong as well as the sizes but they are not, so our first conclusion is that there’s a missing bit of code somewhere. But we had to move one so left physics aside for the moment and unfortunately can’t suggest a fix at the moment – specially since we now have the new PIXI public release.

To conclude, I’d say that Phaser is really great in doing most of the hard work, and has a ton of nice features. But managing multi-resolution assets correctly is crucial. And some bugs should be finally buried. Knowing Photonstorm hard work, we don’t have any doubt this will be fixed in a matter of weeks or months.

We just had to point this problem out, as we see in the Air/flash world, multi-resolution is an ongoing discussion, but why is it so ? we already have solutions ! even if you plan on rasterizing assets at runtime and don’t want to create different asset sizes but only the size that matters !

So remember to make your voice heard on this issue, or if you are one that could help contribute to the Phaser project, please do !

edit 05/04/2015 (Thomas) : Hi guys. So I’m pushing commits to the repository created for our projects that we’re sharing here.
The phasermultires.objects package is just something I’m personally experimenting with right now , and also to explore creating p2 bodies by myself and see if I can fix the physics issues we’re talking about here – I think I’m getting somewhere now, but I just wanted to let you know you shouldn’t pay attention to it (it’s just that now we’re using this repo for our project directly so we can share everything but some things are no longer related to this article).

6 thoughts on “Phaser and multi-resolutions textures issue

  1. Pixi never supported this, so it was something Phaser was never able to support either.

    It’s been part of the design plans for Phaser 3 since the start, so will absolutely be covered in there.

    It’s highly unlikely we will be able to merge Pixi 3 into Phaser 2 – it’s just changed too much internally, and we’ve never used Pixi in the way they intended with their release builds, so it’s also highly unlikely to ever be part of any Phaser 2 version I’m afraid.

    Depending on how long Phaser 3 will take we may revisit Pixi 3 in a couple of months – which will give it time to stabilise as well, as it’s still very fresh and they’re releasing new builds every other day as issues are fixed.

  2. Hi Richard (nice to see you there, by the way) !

    Well, Pixi supported this in V2, but just for @2x. It was in the PIXI.BaseTexture.fromImage method. We updated this method with the small Pixi V3 changements for this part (more or less 15 lines of code in 3 classes) but the real issue is that this method isn’t called by Phaser.

    Anyway I’m glad to know that this will supported in Phaser V3.

  3. Hi Aymeric . I’m glad to hear about your posts here because there is no post on citrus-engine blog .
    you always inspire me technologies , and frameworks , but i have a question , you could not you Haxe + Flambe ? since it is a very powerful tool , and i know that you master Haxe , and you export to any platform not only html5 as it is for Phaser , what is the point to use it rather than Flambe ?

  4. Hey, thanks for the nice words. Well, Phaser comes as a game engine whereas Flambe is mostly a display engine, so I wanted to test the tool included 😉

  5. Is this supported in Phaser 3? I cant find it anywhere, my sh/hd/etc TexturePacker atlas textures (eg ‘scaling variants’ with appropriate ‘scale’ information inside their atlas json files) still look differently scaled, when imported to Phaser3. Can’t find any multi-texture tutorials for Phaser3 either (except old articles like this one).

Leave a Reply

Your email address will not be published. Required fields are marked *