+3 votes

I use the following script to upscale my graphics in a way my pixel art doesn't get distorted.
Because my base resolution is pretty small, i have the problem that it has to be upscaled several times to fill big resolutions. This makes fonts and gui pictures, blurry and blocky, because they are just upscaled lowres fonts, where i would prefer a high res one.

What i want to do is, have this upscaled world graphics but still be able to use the real window resolution for my gui layer. I come from Gamemaker, where the GUI layer always used the actual resolution, and I'm looking for a similar solution. Any suggestions on how i could accomplish that?
Thanks in advance on any suggestions.

onready var root = get_tree().get_root()
onready var base_size = root.get_rect().size
onready var display_size=OS.get_screen_size()
onready var maxzoom=1

func _ready():
    #calculate base_size:
    var calculate_size=display_size
    while(calculate_size>=base_size):
        calculate_size=display_size/maxzoom
        maxzoom+=1  
    base_size=calculate_size

    #make screen resizable
    get_tree().connect("screen_resized", self, "_on_screen_resized")
    root.set_as_render_target(true)
    root.set_render_target_update_mode(root.RENDER_TARGET_UPDATE_ALWAYS)
    root.set_render_target_to_screen_rect(root.get_rect())

func _on_screen_resized():
    var new_window_size = OS.get_window_size()
    var scale_w = max(int(new_window_size.x / base_size.x), 1)
    var scale_h = max(int(new_window_size.y / base_size.y), 1)
    var scale = min(scale_w, scale_h)

    #This offset is needed to keep pixels square
    var offset = ((new_window_size / scale) - (new_window_size / scale).floor()) * scale
    var offsethalf = (offset * 0.5).floor()
    root.set_rect(Rect2(offsethalf, new_window_size / scale))
    root.set_render_target_to_screen_rect(Rect2(offsethalf, new_window_size - offset))
in Engine by (81 points)

I'm looking for an answer to a very similar question—basically, selectively scaling certain parts of the world.

1 Answer

+2 votes
Best answer

Just a note, the root of the game is always a viewport, it's how the game renders. So that's why changing stuff with the root will scale up the image.

To make it so only one part of the game scales up you need a separate viewport. I haven't really messed with viewports before, so I don't know if this is completely the correct way, or the best way to do this. But I think it's working.

This is what the main scene looks like.

Main Scene

There is a ColorFrame, which stretches to the whole size of the screen (using "Full Rect" anchor). And the color is set to black, this is only so the extra pixels at the edge of the screen are black.

The ViewportSprite is what actually renders the Viewport. You'll have to set the viewport after you create it. The only thing I changed here was turning off the "Centered" option.

The Viewport is where all of the game nodes will go. This is what will be scaling up to render the game. The script for the viewport is this:

extends Viewport

export(NodePath) var viewport_sprite_path

onready var viewport_sprite = get_node(viewport_sprite_path)
onready var base_size = self.get_rect().size

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

func _on_screen_resized():
    var new_window_size = OS.get_window_size()
    
    var scale_w = max(int(new_window_size.x / base_size.x), 1)
    var scale_h = max(int(new_window_size.y / base_size.y), 1)
    var scale = min(scale_w, scale_h)
    
    #This offset is needed to keep pixels square
    var offset = ((new_window_size / scale) - (new_window_size / scale).floor()) * scale
    var offsethalf = (offset * 0.5).floor()
    
    self.set_rect(Rect2(Vector2(), new_window_size / scale))
    viewport_sprite.set_scale(Vector2(1, 1) * scale)
    viewport_sprite.set_pos(offsethalf)

There is an export variable here, it's a NodePath to the ViewportSprite, so you'll have to set that as well. As for all of the options for the Viewport, this is what I've set (The "Rect" option can be set to whatever your base screen resolution is:

enter image description here

It's important to note that all of the children of the Viewport node can't be edited in the editor's viewport, like dragging nodes around or rotating them, it doesn't work. So it's probably best if you make all of the game nodes in a separate scene and then make an instance of that scene to set as the child of the Viewport, making it easier to edit.

The final node is a CanvasLayer, this will hold all of the GUI nodes and wont be scaled up like the Viewport. So everything here will stay native size. Of course this means that you can't have GUI elements that appear behind things in the game, they are separate layers.

by (666 points)
selected by

This works great!
But if now i have another problem. If i wanted to just change the Scene that is a child to the viewport or the CanvasLayer, how would i do that?

I'm having problems changing the Scenes that are child to Viewport and CanvasLayer (your scene tree), if i want to change menu scenes or change the world-map.
How would i adress them correctly for a scene_change()?

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.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.