Pixel Perfect in Godot with Zoom

An overview of how-to pixel perfect in Godot.
Introduction
Pixel Perfect is a term that refers to pixel to pixel in size.
With my experience from Löve2D, pixel perfect is a subject that I wasn’t able to learn nor hear about. Although, once I moved engines to speed up production time. It became a problem. As I mainly used Unity before, I found out that there was no real way to play or figure it out with the engine.
As you can see with the video above, it was obvious that it wasn’t pixel perfect as it was smooth. On top of it, despite the assets being pixelated everything managed to looked high resolution.
Therefore, with the development of Little Strange Dreams, I decided to move from Unity to Godot. As I’ve heard a lot about Godot being much more flexible and preferred when it comes to 2D based games.
Once, I moved engines, I found out that it wasn’t quite as easy as doing just that.
Implementation
In Godot, without much configuration it already seemed like it was better for pixel art related games. Although, it isn’t quite set up yet. In order to achieve pixel perfect, you have to change a few project setings such as the width and height . They are located within the window settings under the display. The values must be your target or reference resolution.
Display > Window > Size > Width
Display > Window > Size > Height
After that, make sure to change the mode to viewport . Under the same section, change the aspect to keep height .
Display > Window > Stretch > Mode
Display > Window > Stretch > Aspect
Now, you could say the zoom function would be as easy as changing the zoom within the camera. Yes, while you could do that, it was not the option I was looking for. To be specific, what I wanted was if it started with a pixel I wanted it to end with a pixel not half-a-pixel nor a quarter of a pixel but a pixel. I dove deep to figure out how to do that and this will serve as a guide to those who want to do the same.
As the project is now set to a pixel perfect project. In order to add a “perfect” zoom, you have to add the following to your project:
- ViewportContainer
- Viewport (Viewport_Zoom.gd)
The viewport, will serve as the camera. While the camera will be set to follow the player. With that done, the following code DynamicViewport.gd will be set as a autoload script, what this does is it sets the viewport’s container to the reference width and height. Why? Since the zoom will mainly be done using the viewport. Why? Again, I wanted something specific. With the other code, it’ll be set to the viewport.
If you wish to change the element’s name, make sure to change them in the following code to work properly as they are only set by their default names. In addition, all other elements in the current project must be under the viewport.
The following code below should be added as an autoload script. If you have further questions, don’t hesitate to send a message!
Code
#File: "DynamicViewport.gd"
extends Node
onready var root = get_tree().root
onready var base_size = root.get_visible_rect().size
func _ready():
var scene = get_tree().get_current_scene()
var scene_Viewport = scene.get_node("ViewportContainer")
scene_Viewport.rect_size = Vector2(root.get_size())
scene_Viewport.get_node("Viewport").size = Vector2(root.get_size())
print(scene_Viewport.get_size())
#File: "Viewport_Zoom.gd"
extends Viewport
export var target_size = Vector2(0,0)
onready var temp_y = size.y
var base_Size = Vector2(size.x, size.y)
func _onready():
pass
func experiment():
if size.x > base_Size.x/2 and size.y > base_Size.y/2:
size.x = move_toward(size.x, target_size.x, <reference width ratio>>)
size.y = move_toward(size.y, target_size.y, <<reference height ratio>>)
func zoom_in():
size.x = move_toward(size.x, <<reference width>>, <<reference width ratio>>)
size.y = move_toward(size.y, <<reference height>>, <<reference height ratio>>)
func zoom_out():
while size.x < target_size.x and size.y < target_size.y:
size.x = size.x + 2
temp_y = temp_y + 1.125
size.y = int(temp_y)
#pass
func _process(_delta):
if Input.is_action_pressed("ui_up"):
experiment()
elif Input.is_action_pressed("ui_down"):
zoom_in()