Godot-proposals: Add a project setting to force an integer scale for window stretch

Created on 14 Oct 2020  路  11Comments  路  Source: godotengine/godot-proposals

Describe the project you are working on:
Low resolution 2D pixel art games.

Describe the problem or limitation you are having in your project:
Base engine stretch modes can preserve aspect ratio, but do not attempt to guarantee an even and square pixel grid.

Describe the feature / enhancement and how it helps to overcome the problem or limitation:
New project setting checkbox located at display/window/stretch labelled "Force Integer Scale" (or similar) which changes stretch behavior to attempt preservation of square pixels.

Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams:
I've already produced the feature for 3.2.X here: Yukitty/godot@42af4d50e696e790e33a0d9347fbe55e06da13e8
I am personally using this feature for all current and future 2D projects, as I do not produce or use high resolution art assets.

Ideal test project settings:

  • display/window/size/width and height set to something low, like 256x224 or 320x240
    (I am leaving display/window/size/test_width and test_height set to 1280x720 even in production)
  • display/window/size/resizable On.
  • display/window/stretch/mode 2d
  • Use pixel art assets (Filter OFF) and pixel snap to produce a scene.

Test fiddling with these settings:

  • display/window/stretch/integer_scale (the new feature)
  • display/window/stretch/aspect (different behaviors per setting)

Demonstrated behavior while Integer Scale is active:

  • If display/window/stretch/aspect is set to Keep, the game render will be letterboxed on ALL sides as the window expands, jumping up to the next available multiple of the base resolution _only_ when both the window's width and height are adequate to support it.
  • If aspect is set to Keep Width or Keep Height, the game window will stretch vertically or horizontally, respectively, to almost fill the available space (discarding fractional pixels to preserve pixel accuracy). New letterbox bars will appear on the "kept" sides, which only jumps to resolution multiples.
  • If aspect is set to Expand, the game's aspect ratio will expand in both directions simultaneously, which is another entirely new behavior.
  • In all cases, if integer scale is checked, the pixels should be square, at all resolutions and window sizes, at all times. There may be minor rounding errors in my implementation, but the result is still notably better.

If this enhancement will not be used often, can it be worked around with a few lines of script?:
Almost. The primary blocker preventing a fully scripted solution from being viable is actually the font overdraw setting, which cannot be accessed outside of the SceneTree's root viewport stretch project settings, and is crucial for making dynamic fonts look good on a low resolution screen that's been scaled up.

Even avoiding that issue by using a pixel font, this behavior requires an obscure script and is not easily accessible to many users, as it's not a native setting of Viewports nor ViewportContainers that would display them.

There may be enough potential users to warrant a built-in simple setting, the same users of the Pixel Snap setting which currently work around this issue differently, by limiting the window size to specific game-enforced resolutions, or disabling the stretch feature entirely and writing it off as unusable.

Is there a reason why this should be core and not an add-on in the asset library?:
I can't currently fully replicate this behavior as an add-on due to the font overdraw, as described above.
Otherwise it may be viable, though it seems like an awfully minor feature to bury in the asset library. 馃
It seems more suitably described as a base engine behavior rather than a key feature specific to any particular game or genre.

core rendering

All 11 comments

When working on https://github.com/godotengine/godot/pull/21446, I thought about adding a "Stretch Force Integer Scale" checkbox which would constrain the final scaling ratio to the nearest integer factor. I would implement that in a separate pull request, still.

1. For pixel games, it's better to use Viewport mode instead of 2D.

2. Here's my version of the script:

func _ready():
    get_tree().connect("screen_resized", self, "_on_screen_resized")

func _on_screen_resized():
    var screen = OS.get_screen_size() if OS.window_fullscreen else OS.window_size
    var k = min(int(screen.x / 320), int(screen.y / 180))
    var size = k * Vector2(320, 180)
    var pos = ((screen - size) / 2).floor()
    get_tree().root.set_attach_to_screen_rect(Rect2(pos, size))
    var end = screen - pos - size
    VisualServer.black_bars_set_margins(pos.x, pos.y, end.x, end.y)

3. If it will works out of the box (Pixel Perfect mode), it will be great. :smiley:

4. In addition, we also need the black_bars_bg_color and black_bars_bg_image settings.

Fine~ I attempted to make a fully-featured addon package as well.

The main differences being,

  1. since the plugin and/or autoload has to be enabled in order for the functionality to exist, there's no reason to include a separate checkbox to enable it
  2. for some reason I opted to add a separate resolution option instead of mis-using the "test resolution" setting again
  3. dynamic font overdraw doesn't work (obviously), but that's not a big loss.

So I suppose you can use that as a more accessible prototype implementation now too.
I do not believe this is a simple script to produce, and would still prefer a native setting, especially if more of the imperfect geometry can be fixed... 馃槗

I also have not gotten good results from the viewport scaling mode, counter-intuitively. It just makes the slight noise more noticeable.

Being able to turn the excessive letterboxing into a proper boarder as a color and/or texture would be neat, yes. 馃憤馃徑 I think you can kind of do that currently with how I handle the "Expand" aspect ratio mode, but I didn't put a reasonable limit on how much it would expand if you pull the window ultra-widescreen or whatever...

Ohh, is it the physics/camera that are messing everything up now? Of course, only the individual polygons get snapped to pixels, the actual camera offset and object positions are still all over the place... 馃槗 Seems like this might take more work still.

@Yukitty I looked at your addon and left some comments. This is a very good prototype. :+1:

The only thing, it seems to me, that this function should be implemented not by a checkbox, but as a separate stretch_mode (as a stricter version of the Viewport mode).

It doesn鈥檛 combine with disabled mode in any way, with 2d mode it is formally combined, but it looks like slyness.

Here's what the documentation says about it:

Stretch Mode = 2D: <...> This is a good option if your 2D artwork has a sufficiently high resolution and does not require pixel-perfect rendering.

2d mode adds new details for lines, fonts, etc.:

(That is, this cannot be called _pixel perfect mode_ in any way. :rofl: )

This is "wrong" for pixel games, but okay. We can simply point this out in the documentation, but not take away the choice from users. However, the number of combinations of different modes, aspects and this new feature is truly impressive.

Yeah, I was just confused because viewport was looking mighty terrible, solely because my character and camera were moving on subpixels it turns out, and Pixel Snap by itself does not seem to 100% ensure things won't come up weird/inconsistent sometimes, especially at higher resolutions? 馃槗

I had to rewrite my whole player physics to work on a completely separate hidden layer with the sprites just following the whole-number positions around and a custom camera Node2D script doing the same...

@Yukitty This will be fixed by https://github.com/godotengine/godot/pull/41535 if it's merged.

@asheraryam No, this is a different issue. This proposal is about adding an option to constrain automatic viewport scaling to an integer factor.

In case anyone has come across this and wants a visual explanation:

  • Here's a perfectly integer-scaled 2D scene (at 8 pixels to 1):

    Screenshot from 2020-11-19 20-12-29

  • Here's that same scene with the scale just slightly off an integer. Note the character's eyes:

    Screenshot from 2020-11-19 20-12-51

The latter is what this proposal is seeking to avoid. For me, this often happens when my game is in windowed mode and the window gets resized (away from a nice integer-scaled size).

From my experience, if expanding viewport is used for pixel perfect scaling (that is, when the whole game window is covered, giving different view area for different aspect ratios) - it's desirable to have a callback where game developer can override integer factor for currently set window width/height.

This is because games have different metrics of what size of area the player needs to see. For example, in one of the games we used about 9 tiles per window height as ideal metric, and from all integer factors found the one to achieve the closest to this metric (as opposed to fixed view area with camera auto-stretched based on "as thin as possible black bars on sides metric").

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Torguen picture Torguen  路  3Comments

SpyrexDE picture SpyrexDE  路  3Comments

wijat picture wijat  路  3Comments

WizzardMaker picture WizzardMaker  路  3Comments

regakakobigman picture regakakobigman  路  3Comments