phaserjs / phaser

Phaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering.
https://phaser.io
MIT License
36.94k stars 7.08k forks source link

[Feature Request] Real stage resizing #642

Closed georgiee closed 10 years ago

georgiee commented 10 years ago

Hello, I really wonder why there is no 'real' stage resizing in phaser. You can only pass in some width and height and rely on SHOW_ALL or EXACT_FIT to scale your whole game- respecting your chosen aspect ration of width & height. As far as I know there is no built in way of increasing the phaser stage/world size. For those of us who know flash I mean that behaviour:

stage.scaleMode = StageScaleMode.NO_SCALE
stage.align = StageAlign.TOP_LEFT
stage.addEventListener(Event.RESIZE, onStageResized)

function onStageResized(e:Event){
    trace('stage dimensions changed', stage.stageWidth, stage.stageHeight)
}

You will get a stage that fills the whole container. Inside your AS3 you can then listen for a change and adopt all of your elements to the new dimensions (if you like).

The missing of this basic feature- is this an already made decision from the past or simply a feature not yet requested that much (I've never seen a direct request for this only question around scaling). Is something else technically standing in the way to do this? A real stage resizing would make it easy to create games filling the whole viewport either in landscape or portrait, wouldn't it ?

Thanks for an answer!

BurnedToast commented 10 years ago

+1 I would like to do such a nonscaling resize too

georgiee commented 10 years ago

I just looked into this as I want my wrong orientation sprite not to be scaled with a wrong aspect ratio. This is always the case at the moment( as the orientation change is triggered by a resize). This orientation sprite looks really terrible. Wouldn't it be as easy as this ?

//new method in scale manager
ScaleManager#resizeCanvasToWindow: function () {
        this.game.renderer.resize(window.innerWidth, window.innerHeight)
        this.width = this.game.width = window.innerWidth
        this.height = this.game.height = window.innerHeight

        this.game.world.setBounds(0, 0, this.game.width, this.game.height);

        if(this.orientationSprite){
          this.orientationSprite.x = this.game.width / 2
          this.orientationSprite.y = this.game.height / 2
        }
    },

//call that method in ScaleManager#checkResize
ScaleManager#checkResize: function (event) {    
    ///[...]
    if (this.scaleMode !== Phaser.ScaleManager.NO_SCALE)
     {
     this.refresh();
    //this branch is new and triggers the resize in mode no_scale (the other mode are modifying too much so I picked this one for my testing.
    }else{
      this.resizeCanvasToWindow();
    }

The core of this all is pixi's renderer.resize(window.innerWidth, window.innerHeight) the rest is updating phaser's bounds and dimensions. Worth to try a real mode a la ScaleManager.CANVAS_RESIZE or is there a reason that this is not done yet ?

Thanks for an answer.

photonstorm commented 10 years ago

I meant to remove the orientation image actually, which is why all the Project Templates do it via the DOM (far more accurately). Either way that's an aside to the actual request.

I don't mind adding support for this, but I'm not sure it's as easy as just resizing the renderer. Or rather, it is that easy if that's all I want to support, but it means on a resize any Sprites already on the display list will now be in very weird locations unless you've written custom code for them to monitor the game size and adjust themselves accordingly.

Maybe I shouldn't care about this though? The Stage resizes and it's up to you to sort out the coordinate mess that will arise. All I need to do is update the world and input scales.

Could work as a 'stage 1' for this. Where Sprites positioned based on percentages or alignment values (i.e. "bottom-right") rather than pixels could be a stage 2 feature to come later.

georgiee commented 10 years ago

Ah good to know that you're going to remove it. So I can drop my workaround. I focussed on finding a solution inside of phaser but this just convinced me to try the DOM solution. I controlled that coordinate mess in flash so it should be controllable in phaser ;) I mean that was THE way to go in flash (NO_SCALE, TOP_LEFT, listen to Event.RESIZE) and opened the doors for some beautiful liquid websites. I can think about situations where this could get handy for some games. You could fill small px differences between devices that show up as black bars in the current SHOW_ALL mode. Maybe you want to match the world bounds with the measurements of the device to create worlds that are unscaled & crispy on all devices and use every pixel available . These two use cases alone are far more useful than a scaled version of my game with a wrong aspect ratio like you get with EXACT_FIT. Does anybody really use this scaling mode in the wild? :)

So yeah. Just don't care for the mess :) Give use the possibility to use every unscaled pixel by updating what is needed in the core and dispatch the resize event. From then on the rest in on us! Promised! Thank you for considering this feature!

mariogarranz commented 10 years ago

I am really interested in this feature. Are there any plans to do that 'stage 1' you mentioned time ago, Rich?

Else, I wouldn't mind trying to code some of this stuff myself, could you give me any indications on which properties I should be looking into? According to your previous messages I understand Game.World is a good starting point, but I guess more objects may be affected about this change.

Harissa commented 10 years ago

I'm interesting in this feature and having a go at implementing it myself. The way I'd like it work is: Create a new ScaleManager Mode - maybe called FILL_SCREEN_FIXED_ASPECT Set a maximum height and width for the game to work at When the window is resized the canvas is resized to fill it The game scale for both width and height is changed to make the game fill the canvas in one direction. The overlap is handled by moving the origin of the co-ordinate space or adding extra "space" on the end. So I don't need to move or rescale everything which is already on the screen.

For example: I have a game which has a maximum size of 480 x 320. I play it on a screen of 1136 x 640. The game gets scaled by a factor of 2 and the x co-ordinate system now runs from -44 to 524. Any sprites in the main playing area are automatically moved and scaled by Phaser - the only bit that changes are the "edge" areas which I need to update myself.

As I said, that's the plan. I'm guessing that for it to work I'll need to see if I can move the game origin and still have everything work. Any comments or suggestions welcome.

photonstorm commented 10 years ago

How does it do it in Flash @georgiee ? (it's been a while, I forget :)) I thought you had a Stage size of say 640x480 with origin at top-left (0x0), the window gets larger, say 1280x700 and basically you've just gained a load of space on the right of your Stage and the origin remains 0x0 top left.

georgiee commented 10 years ago

You nailed it. Simple as this! :) Combine it with a resize event and a user can do whatever he wants with the additional space on the lower right corner.

Harissa commented 10 years ago

Yep, that sounds good to me. Users can then scale and align the whole game accordingly using Mathieu's technique of creating a "world" group which contains everything and you can scale and translate it as you like. http://www.html5gamedevs.com/topic/1380-how-to-scale-entire-game-up/

Harissa commented 10 years ago

Ok, I've been having a go at this and I think I've got the ScaleManager bit working. Here's the changes I've made to the ScaleManager

// add new scale mode
Phaser.ScaleManager.FILL_WINDOW_FIXED_ASPECT = 3;

 /**
    * Set screen size automatically based on the scaleMode.
    * @param {boolean} force - If force is true it will try to resize the game regardless of the document dimensions.
    */
    setScreenSize: function (force) {

        // Scroll code the same and omitted for brevity       

        if (force || window.innerHeight > this._startHeight || this._iterations < 0)
        {
            // Set minimum height of content to new window height
            document.documentElement['style'].minHeight = window.innerHeight + 'px';

            if (this.incorrectOrientation)
            {
                this.setMaximum();
            }
            else if (!this.isFullScreen)
            {
               // add new scale mode - change to switch statement 
                switch (this.scaleMode) {
                    case Phaser.ScaleManager.EXACT_FIT:
                        this.setExactFit();
                        break;
                    case Phaser.ScaleManager.SHOW_ALL:
                        this.setShowAll();
                        break;
                    case Phaser.ScaleManager.FILL_WINDOW_FIXED_ASPECT:
                        this.fillWindow();
                        break;
                }
            }

            else
            {
                 // add new scale mode to full screen- change to switch statement 
                switch (this.fullScreenScaleMode) {
                    case Phaser.ScaleManager.EXACT_FIT:
                        this.setExactFit();
                        break;
                    case Phaser.ScaleManager.SHOW_ALL:
                        this.setShowAll();
                        break;
                    case Phaser.ScaleManager.FILL_WINDOW_FIXED_ASPECT:
                        this.fillWindow();
                        break;
                }
            }

            this.setSize();
            clearInterval(this._check);
            this._check = null;
        }

    }

 /**
    *    New function to resizes canvas to fill the whole window
    *   @method Phaser.ScaleManager#resizeCanvasToWindow
    */
    fillWindow: function () {
        this.game.renderer.resize(window.innerWidth, window.innerHeight);
        this.width = window.innerWidth;
        this.height = window.innerHeight;
    },

setSize: function () {

    // all the alignment orientation code exactly the same and omitted for clarity

        this.scaleFactor.x = this.game.width / this.width;
        this.scaleFactor.y = this.game.height / this.height;

 // Check to see if the aspect ratio is fixed and change the scale factor accordingly

        if (this.scaleMode !== Phaser.ScaleManager.FILL_WINDOW_FIXED_ASPECT) {
            this.aspectRatio = this.width / this.height;
            this.scaleFactorInversed.x = this.width / this.game.width;
            this.scaleFactorInversed.y = this.height / this.game.height;

        } else {
            var actualScale = Math.max(this.scaleFactor.x, this.scaleFactor.y);
            this.scaleFactor.x = actualScale;
            this.scaleFactor.y = actualScale;
            this.scaleFactorInversed.x = 1 / actualScale;
            this.scaleFactorInversed.y = 1 / actualScale;
            this.aspectRatio = this.game.width / this.game.height;
        }
        this.game.input.scale.setTo(this.scaleFactor.x, this.scaleFactor.y);

        this.hasResized.dispatch(this.width, this.height);

        this.checkOrientationState();

    }

It seems to be working fairly well. It resizes the canvas based to fill the window ok. I've been working on getting the game sprites to scale and move according to the size of the canvas but I'm finding that the sprites are still getting stretched occasionally for some reason.. I suspect it's something to do with the relationship between the stage, the world and canvas which I'm not quite getting. I'll spend a bit more time and post any more progress.

lucbloom commented 10 years ago

In our cross platform C++ (GameHouse/Blue Giraffe) engine we have more than one aspect ratio: the maxAR and minAR. We usually set minAR to 4/3 and maxAR to 16/9 (although Android devices go up to 16/8.5).

Then you can lock the AR to either "width" or "height". What this means is that the renderer always either shows you the full height, but clips off a part of the sides in 4/3, - or - shows you the full width, but clips off a part of the top&bottom in 16/9.

It's important that no pixels are squashed (square AR per pixel) by AR sizes within the min/maxAR, but we have another feature: when the screen gets too wide, we can control how much we let the renderer 'stretch' the game to prevent black bars. (Usually up to 16/8). Stretching in height is just plain ugly so that's not allowed.

If the AR goes even beyond these min/max values plus allowedStretch, the stage is surrounded by black bars of course (letterboxing).

One last feature could be: set a 'cut off fraction' so that when the game is cut off at the top&bottom (locked to width), you can control how much is cut off at the top and how much on the bottom. 0 would mean "snap to top", 0.5 "middle cut off", etc. In our games for instance, we'd rather cut off some of the top than the bottom, so we set it to 0.8. The amount that got cut off is a static variable accessible in the engine, cutoffOffset {x, y}, you can use in your game to position the menu bars etc.

We're doing the first part of this technique now for our HTML5 venture and I wouldn't want it any other way :-) I'll try to send you a video later on.

lucbloom commented 10 years ago

(Don't mind the programmer art and lack of originality of this try-out :-) http://youtu.be/UF2EbgATFvE

photonstorm commented 10 years ago

Thanks for the insights and video. We already use a concept of min/max ARs and letter boxing, but the cutoffOffset sounds very useful. What isn't exposed though is a proper resize event. The renderer remains fixed at the size it was generated. Mostly because in practise game sizes rarely actually change. People don't tend to scale their browser windows around much on desktop, and on mobile they can't anyway.

With a proper resize event the developers can do with that whatever they like, just like they did for years in Flash, including adjusting UI placements, cropping, etc.

It's also important to factor in that the device resolution is rarely used as the game resolution. Very often the actual canvas size and quantity of pixels is quite smaller than what the device is capable of, and for good reason too: speed. There are hundreds of thousands of devices out there that simply can't render canvas fast enough at their actual resolutions, so we up-scale. When WebGL finally lands everywhere (yay! thanks Apple!) this will be much less of an issue :) But it certainly was a year ago when we built this and still is really.

However there are of course cases for sure when you absolutely need to fill every last single pixel natively (i.e. when you can ignore crappy legacy browsers!), so we'll definitely be looking at adding proper resize support soon.

mariogarranz commented 10 years ago

I was about to point at what Rich just said. From my point of view, it's interesting being able to modify the canvas size to fit a given aspect ratio, but still maintain the possibility to use EXACT_FIT and SHOW_ALL to scale the game via CSS.

Just like he said, many games perform way better, specially on stock browsers, when rendered in a smaller canvas and resized via CSS.

Thank you all guys for helping with this and thanks Rich for taking note, it's a really interesting feature.

dillydadally commented 10 years ago

Just wanted to pipe in and say this was one of the first things I looked for when I picked up Phaser. It seems to me to be a very important feature. Lets get this done.

photonstorm commented 10 years ago

And 5 months later, support for this is now included :) Going to close this issue off, but feel free to open new ones if you find something wrong with the implementation, but it's working well across all my tests so far.

georgiee commented 10 years ago

Awesome!! Thank you Rich! I'm looking forward to try some flash like alignments finally in phaser :)

jdnichollsc commented 9 years ago

How is possible to make resize like http://flashvhtml.com/ in Phaser? PD: excuse my bad english, i know spanish :P