+14 votes

Introduction

I am having a problem bulding responsiveness.

The build-in methods of stretching and scaling, found in the project settings, is not quiet what i want, or at least i can't make them behave as i want.

I kind of want a mix of the "keep aspect ratio" and ignoring stretching completely.

I want the game to scale, keeping the aspect ratio, as long a certain area of the game fits, and if the aspect ratio doesn't fill the whole screen i just want more of the game to be shown.

More in depth explanation - With fancy pictures and everything
Full
The red frame is the minimum, and should always be shown.
As long as this minimum frame of the game can be scaled up (keeping the aspect ratio) the whole game should just scale up like the "keep aspect ratio" option would do.

If the screen has more room to the sides or at the top/bottom, more of the background should just be visible instead of adding black bars or such.

For example running on a mobile in portrait mode it would look like this:
Portrait

Running on a mobile in landscape mode it would look like this:
Landscape

Summary

  • How can i dynamically define how much of my game should be shown at runtime?

So i can just show more of my background instead of black bars/clear color generated by Godot, of course as long as my texture is big enough.

  • How can i define a minimum size?

So i make sure my whole game is always visible, and at the same time i have a "zeroing point" to base the scaling off.

  • How do i scale up the game, keeping the aspect ratio of the defined minimum size?

So my game scales to fit all resolutions, just like the build-in "keep aspect ratio stretch_mode" would.

Side notes
I have played a bunch around with the stretching and scaling, i have tried a few ideas i had with controlling the viewport of the root node through scripting.

I just really can't seem to find a mix of the functionalities already in Godot.

I am looking forward to your ideas, suggestions, solutions or whatever you might have to add.

Thanks
Keetz

asked Nov 14, 2016 in Engine by Rasmus (293 points)
edited Nov 17, 2016 by Rasmus

1 Answer

+13 votes
Best answer

I have come up with a solution, so for everyone looking for this kind of behavior, here is one way it can be done.

I would like to thanks bojidar-bg (https://github.com/bojidar-bg) for helping me out on IRC by directing me to a previous game that has similar behavior, https://github.com/KOBUGE-Games/minilens

Most of the scripting is based on this game, so if my solution doesn't quiet fit for you, give minilens a look yourself ;)

TL;DR
This ended up being quiet long so i added a TL;DR at the bottom, if you know your way around Godot.

All right, let's get on with it!
First of all we need to setup our project settings and our scene. I will just show the bare minimum scene structure.

In Project Settings->Display i set the width and height to my minimum size so i have a reference for setting up my scene correct. Again, it is just a reference so it is easy to visualize how much is visible at all times, we will manipulate the actual size of the viewport later in script.

Next up in Project Settings->Display we should set the stretch_mode to 2d and the stretch_aspect to ignore, have a look at http://docs.godotengine.org/en/stable/tutorials/engine/multiple_resolutions.html for more information on how stretching works.

That's it for Project Settings.

Next up we add some stuff to our scene, we need a Control with a child Sprite.

Simple scene setup
The Texture on the sprite is our oversized background that goes beyond the minimum size we want, and the Sprite itself is positioned to the center of our minimum sized frame. For example check the Centered property in the Inspector and set the Position to (minimum width/2, minimum height/2).
The Control nodes anchor is set to Center so our background stays centered.

With our current project setup our image will just stretch and skew to whatever dimensions are needed to fill out the window, thanks to the stretch_mode and stretch_aspect.

Time to code!

This is where we set our minimum size that actual matters, the scaling will be based on the size we set here in the script, and not the Project Settings that we set earlier.

We will make use of the signal "size_changed" found on a Viewport (http://docs.godotengine.org/en/stable/classes/class_viewport.html#signals) so we don't have to check for changes in screen size every frame, every second or whatever.

Whenever the screen size changes, we:

  • Calculate a scale factor based on the minimum height and the current height of the screen
  • Calculate a new size for the viewport
  • Check if we should scale down (if the new width/height is smaller than our minimum width/height)
  • Set the new size on our viewport

I wont explain the math itself, but it is really not that advanced and it should be easy to find examples and explanations around the web for simple scaling like this.

The script, again bare minimum.

extends Node

onready var viewport = get_viewport()

var minimum_size = Vector2(1920, 1080)

func _ready():
    viewport.connect("size_changed", self, "window_resize")
    window_resize()

func window_resize():
    var current_size = OS.get_window_size()

    var scale_factor = minimum_size.y/current_size.y
    var new_size = Vector2(current_size.x*scale_factor, minimum_size.y)

    if new_size.y < minimum_size.y:
        scale_factor = minimum_size.y/new_size.y
        new_size = Vector2(new_size.x*scale_factor, minimum_size.y)
    if new_size.x < minimum_size.x:
        scale_factor = minimum_size.x/new_size.x
        new_size = Vector2(minimum_size.x, new_size.y*scale_factor)

    viewport.set_size_override(true, new_size)

Last up we have to save our script and then add it as a singleton in Project Settings -> AutoLoad tab (http://docs.godotengine.org/en/stable/tutorials/step_by_step/singletons_autoload.html).

EDIT
I just thought i would add a screenshot of a setup where there is a game node added, to show how we make sure our game stays centered on the background.

With Game Node
So we have a Main node that just contains the rest of our game.
Then we have two Control nodes, one which is our background from earlier, and the other Control node is where we place our actual game (I have just added the godot logo as a sprite as an example).

And to make sure our game stays at the same position, we set the GameControl Anchor to Center just like we did with the background.

And voila! we now have a setup where we can replace the SomeGameSprite with a Scene containing our actual game.

TL;DR
- In Project Settings->Display set stretch_mode = 2d and stretch_aspect = ignore.
- Create a Control and anchor it to Center, then add a Sprite as a child and set your oversized background as the texture and center it.
- Add the script, seen further up this answer, to AutoLoad

answered Nov 16, 2016 by Rasmus (293 points)
selected Dec 30, 2016 by Rasmus

By the way this is a perfect example for a case where Autoload is the right solution.
As much as I criticize how most people use singletons I thought I should point that out.

It's a good example, because the script doesn't access any part of the scene tree, so it will work no matter what scene is the main scene, and it is by definition working on the application itself, so if you were to change the game so that the current main scene becomes a subscene of a new menu or something you would have to move this script to the new main scene if if wasn't an autoload script.

I followed your instructions but I did not get.
Something is wrong, Sprite is not properly centered

Here are the results

enter image description here
enter image description here
enter image description here

P.S. Godot 2.1

My bad, Sprite has to be centered like this:
enter image description here

Ah yes i can see that it can be misunderstood, glad you got it working anyways.
I will edit my instructions to be more clear, hopefully.

Another way to center stuff is to calculate the translation required and using viewport.set_global_canvas_transform to center. The advantage is it doesn't require any "centered" Control containers (which I couldn't get to work for some reason).

The idea of using the Control node is to let Godot's anchoring handle those calculations.

But if you want to, it is pretty basic indeed, something like this would do:

background.set_pos(Vector2(viewport.x/2, viewport.y/2))

But again, the idea was, why do this manually when Godot already does it for us?

If you want, i would be happy to help you out with the problems you have with the Control node.

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.