├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── addons └── fuzzy_display_tools │ ├── FuzzyDisplayTools.gd │ ├── LICENSE │ ├── example_scene │ ├── CameraFollow.gd │ ├── ExampleUI.gd │ ├── ExampleUI.tscn │ ├── FuzzyDisplayToolsExample.tscn │ └── Player.gd │ ├── nodes │ ├── FuzzyCamera2D.gd │ ├── FuzzyViewport.gd │ └── FuzzyViewportContainer.gd │ ├── plugin.cfg │ └── singletons │ └── FuzzyViewportScaler.gd ├── default_env.tres ├── icon.png ├── icon.png.import ├── logo.png ├── logo.png.import └── project.godot /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Godot-specific ignores 3 | .import/ 4 | export.cfg 5 | export_presets.cfg 6 | 7 | # Mono-specific ignores 8 | .mono/ 9 | data_*/ 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 fossegutten 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FuzzyDisplayTools 2 | * Addon for Godot Engine 3.1 and 3.2 3 | * Display tools, mostly for 2D games, especially good for pixel art. 4 | * Includes an improved Camera2D and a convenient singleton with automatic viewport resizing. 5 | 6 | ## Features: 7 | ### FuzzyViewportScaler - Singleton: 8 | * Automatically resizes the main viewport, calculated from base window size in ProjectSettings. 9 | * Support for stretch, keep aspect and integer scaling. 10 | * Pixel perfect mode is also possible, for the purist! 11 | 12 | ### FuzzyCamera2D 13 | * No jittering. 14 | * No 1-frame lag/delay, like default camera ( If used properly ). 15 | * Virtual size support, automatically zooms in to get the specified resolution. 16 | 17 | ### FuzzyViewportContainer and FuzzyViewport: 18 | * Utility scripts, to automatically resize UI viewport when needed (after FuzzyViewportScaler updates scale). 19 | * Updates ViewportContainer shrink automatically. 20 | 21 | ## Initial Setup: 22 | ### ProjectSettings required settings: 23 | * "display/window/stretch/mode" = "viewport" 24 | * "display/window/stretch/aspect" = "ignore" 25 | 26 | ### ProjectSettings recommended settings: 27 | * "display/window/dpi/allow_hidpi" = true 28 | * Strongly recommended because we get pixel distortions if users don't have integer scaling in the OS (125, 150, 175, 250% scaling etc.). 29 | * "display/window/size/width" = 320 / 640 etc. 30 | * "display/window/size/height" = 180 / 360 etc. 31 | * Some low resolution with 16/9 aspect ratio values is recommended, for modern monitors. 32 | * "rendering/quality/dynamic_fonts/use_oversampling" = false 33 | * Removes annoying warnings from the Debugger, when resizing window. 34 | 35 | 36 | ## How to use: 37 | ### FuzzyViewportScaler - Singleton: 38 | * Should be added automatically in AutoLoad, when the Addon is activated. 39 | * Customize the custom values in ProjectSettings: 40 | * "display/fuzzy_display_tools/scale_mode" (enum) 41 | * "display/fuzzy_display_tools/pixel_perfect" (bool) 42 | 43 | ### FuzzyCamera2D: 44 | * Using virtual size is strongly recommended. Set it to same values (or 50% / 25% etc.) as window size in ProjectSettings, to get the best results. 45 | * Especially useful if not using pixel perfect mode. 46 | * Use zoom_f (float) parameter instead of regular zoom (Vector2). Adjusting regular zoom values does nothing. 47 | * Create a camera follow target node, and set from editor or script. 48 | * Put the camera last in the scene tree, because it should process after the follow target node. This fixes the 1 frame lag/delay from default camera. 49 | 50 | ### FuzzyViewportContainer and FuzzyViewport: 51 | 1. Depends on FuzzyViewportScaler singleton! 52 | 1. Add a CanvasLayer node to your scene. 53 | 1. Add a FuzzyViewportContainer as a child of CanvasLayer. 54 | 1. Add a FuzzyViewport as a child of FuzzyViewportContainer. 55 | 1. Strongly recommended to set the size to the same as window size in ProjectSettings. 56 | 1. Add an instanced UI scene as a child of FuzzyViewport. This is necessary because you cannot edit a scene inside a custom Viewport. 57 | -------------------------------------------------------------------------------- /addons/fuzzy_display_tools/FuzzyDisplayTools.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | const SCALE_MODE_SETTING := "display/fuzzy_display_tools/scale_mode" 5 | const PIXEL_PERFECT_SETTING := "display/fuzzy_display_tools/pixel_perfect" 6 | const INTEGER_SCALING_SETTING := "display/fuzzy_display_tools/integer_scaling" 7 | 8 | const FUZZY_VP_SCALER := "res://addons/fuzzy_display_tools/singletons/FuzzyViewportScaler.gd" 9 | 10 | 11 | func _enter_tree(): 12 | add_autoload_singleton("FuzzyViewportScaler", FUZZY_VP_SCALER) 13 | 14 | # Recommended / required settings 15 | # TODO Add a readme, remove this, and let the user set these manually? 16 | 17 | # Required by the FuzzyViewportScaler 18 | ProjectSettings.set("display/window/stretch/mode", "viewport") 19 | ProjectSettings.set("display/window/stretch/aspect", "ignore") 20 | # Strongly recommended because we get pixel distortions if users don't have integer scaling in the OS (125, 150, 175, 250% scaling etc.) 21 | ProjectSettings.set("display/window/dpi/allow_hidpi", true) 22 | print_debug("FuzzyDisplay tools just changed the following in ProjectSettings:" + \ 23 | "window stretch mode, window stretch aspect and allow_hidpi.") 24 | 25 | # Add settings under ProjectSettings -> Display -> Fuzzy display tools 26 | if !ProjectSettings.has_setting(PIXEL_PERFECT_SETTING): 27 | ProjectSettings.set_setting(PIXEL_PERFECT_SETTING, false) 28 | ProjectSettings.set_initial_value(PIXEL_PERFECT_SETTING, false) 29 | 30 | if !ProjectSettings.has_setting(INTEGER_SCALING_SETTING): 31 | ProjectSettings.set_setting(INTEGER_SCALING_SETTING, false) 32 | ProjectSettings.set_initial_value(INTEGER_SCALING_SETTING, false) 33 | 34 | if !ProjectSettings.has_setting(SCALE_MODE_SETTING): 35 | ProjectSettings.set_setting(SCALE_MODE_SETTING, 0) 36 | ProjectSettings.set_initial_value(SCALE_MODE_SETTING, 0) 37 | 38 | var property_info = { 39 | "name": SCALE_MODE_SETTING, 40 | "type": TYPE_INT, 41 | "hint": PROPERTY_HINT_ENUM, 42 | "hint_string": "stretch,keep_aspect"#,keep_height,keep_width" 43 | } 44 | 45 | ProjectSettings.add_property_info(property_info) 46 | 47 | 48 | func _exit_tree(): 49 | pass 50 | -------------------------------------------------------------------------------- /addons/fuzzy_display_tools/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 fossegutten 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /addons/fuzzy_display_tools/example_scene/CameraFollow.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | 4 | func _process(delta): 5 | var pos : Vector2 6 | 7 | for i in get_tree().get_nodes_in_group("player"): 8 | pos += i.global_position 9 | 10 | global_position = pos / get_tree().get_nodes_in_group("player").size() 11 | -------------------------------------------------------------------------------- /addons/fuzzy_display_tools/example_scene/ExampleUI.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | # Do not run this scene directly. It should be instanced inside: 4 | # res://addons/fuzzy_display_tools/example_scene/FuzzyDisplayToolsExample.tscn 5 | 6 | func _ready(): 7 | $VBoxContainer/PixelPerfectButton.pressed = FuzzyViewportScaler.pixel_perfect 8 | $VBoxContainer/IntegerScalingButton.pressed = FuzzyViewportScaler.integer_scaling 9 | $VBoxContainer/ScaleModeOptionButton.selected = FuzzyViewportScaler.get_scale_mode() 10 | 11 | 12 | func _process(delta): 13 | var l : Label = $Label 14 | 15 | l.text = "Base resolution: %s" % [FuzzyViewportScaler.game_size] 16 | l.text += "\nMain Viewport res: %s" % [$"/root".get_viewport().size] 17 | l.text += "\nMain Viewport scale: %s" % [FuzzyViewportScaler.viewport_scale] 18 | l.text += "\nPixel perfect: %s" % [FuzzyViewportScaler.pixel_perfect] 19 | l.text += "\nInteger scaling: %s" % [FuzzyViewportScaler.integer_scaling] 20 | l.text += "\nP1 controls: Arrow keys" 21 | l.text += "\nP2 controls: WASD" 22 | 23 | 24 | func _on_FullscreenButton_pressed(): 25 | OS.window_fullscreen = !OS.window_fullscreen 26 | 27 | 28 | func _on_PixelPerfectButton_toggled(button_pressed): 29 | FuzzyViewportScaler.set_pixel_perfect(button_pressed) 30 | 31 | 32 | func _on_ScaleModeOptionButton_item_selected(index): 33 | FuzzyViewportScaler.set_scale_mode(index) 34 | 35 | 36 | func _on_IntegerScalingButton_toggled(button_pressed): 37 | FuzzyViewportScaler.set_integer_scaling(button_pressed) 38 | 39 | 40 | func _on_AspectRatioButton_item_selected(index): 41 | var text : String = $VBoxContainer/AspectRatioButton.get_item_text(index) 42 | var size := Vector2.ZERO 43 | 44 | if text == "16/9": 45 | size = Vector2(640,360) 46 | if text == "21/9": 47 | size = Vector2(860, 360) 48 | if text == "4/3": 49 | size = Vector2(480, 360) 50 | if text == "16/10": 51 | size = Vector2(576, 360) 52 | 53 | if !size.is_equal_approx(Vector2.ZERO): 54 | FuzzyViewportScaler.game_size = size 55 | FuzzyViewportScaler.update_viewport_rect() 56 | else: 57 | printerr("Could not update resolution to: %s" % size) 58 | -------------------------------------------------------------------------------- /addons/fuzzy_display_tools/example_scene/ExampleUI.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/fuzzy_display_tools/example_scene/ExampleUI.gd" type="Script" id=1] 4 | 5 | [node name="ExampleUI" type="Control"] 6 | anchor_right = 1.0 7 | anchor_bottom = 1.0 8 | script = ExtResource( 1 ) 9 | __meta__ = { 10 | "_edit_use_anchors_": false 11 | } 12 | 13 | [node name="Label" type="Label" parent="."] 14 | margin_right = 40.0 15 | margin_bottom = 14.0 16 | text = "This is UI" 17 | 18 | [node name="VBoxContainer" type="VBoxContainer" parent="."] 19 | anchor_left = 1.0 20 | anchor_right = 1.0 21 | margin_left = -174.0 22 | margin_bottom = 132.0 23 | __meta__ = { 24 | "_edit_use_anchors_": false 25 | } 26 | 27 | [node name="FullscreenButton" type="Button" parent="VBoxContainer"] 28 | margin_right = 174.0 29 | margin_bottom = 20.0 30 | text = "Fullscreen" 31 | __meta__ = { 32 | "_edit_use_anchors_": false 33 | } 34 | 35 | [node name="ScaleModeOptionButton" type="OptionButton" parent="VBoxContainer"] 36 | margin_top = 24.0 37 | margin_right = 174.0 38 | margin_bottom = 44.0 39 | text = "Stretch" 40 | items = [ "Stretch", null, false, 0, null, "Keep Aspect", null, false, 1, null ] 41 | selected = 0 42 | __meta__ = { 43 | "_edit_use_anchors_": false 44 | } 45 | 46 | [node name="AspectRatioButton" type="OptionButton" parent="VBoxContainer"] 47 | margin_top = 48.0 48 | margin_right = 174.0 49 | margin_bottom = 68.0 50 | text = "16/9" 51 | items = [ "16/9", null, false, 0, null, "21/9", null, false, 1, null, "4/3", null, false, 2, null, "16/10", null, false, 3, null ] 52 | selected = 0 53 | __meta__ = { 54 | "_edit_use_anchors_": false 55 | } 56 | 57 | [node name="PixelPerfectButton" type="CheckButton" parent="VBoxContainer"] 58 | margin_top = 72.0 59 | margin_right = 174.0 60 | margin_bottom = 112.0 61 | text = "Pixel Perfect" 62 | 63 | [node name="IntegerScalingButton" type="CheckButton" parent="VBoxContainer"] 64 | margin_top = 116.0 65 | margin_right = 174.0 66 | margin_bottom = 156.0 67 | text = "Integer Scaling" 68 | 69 | [connection signal="pressed" from="VBoxContainer/FullscreenButton" to="." method="_on_FullscreenButton_pressed"] 70 | [connection signal="item_selected" from="VBoxContainer/ScaleModeOptionButton" to="." method="_on_ScaleModeOptionButton_item_selected"] 71 | [connection signal="item_selected" from="VBoxContainer/AspectRatioButton" to="." method="_on_AspectRatioButton_item_selected"] 72 | [connection signal="toggled" from="VBoxContainer/PixelPerfectButton" to="." method="_on_PixelPerfectButton_toggled"] 73 | [connection signal="toggled" from="VBoxContainer/IntegerScalingButton" to="." method="_on_IntegerScalingButton_toggled"] 74 | -------------------------------------------------------------------------------- /addons/fuzzy_display_tools/example_scene/FuzzyDisplayToolsExample.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=8 format=2] 2 | 3 | [ext_resource path="res://addons/fuzzy_display_tools/nodes/FuzzyCamera2D.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/fuzzy_display_tools/nodes/FuzzyViewportContainer.gd" type="Script" id=2] 5 | [ext_resource path="res://addons/fuzzy_display_tools/nodes/FuzzyViewport.gd" type="Script" id=3] 6 | [ext_resource path="res://icon.png" type="Texture" id=4] 7 | [ext_resource path="res://addons/fuzzy_display_tools/example_scene/Player.gd" type="Script" id=5] 8 | [ext_resource path="res://addons/fuzzy_display_tools/example_scene/ExampleUI.tscn" type="PackedScene" id=6] 9 | [ext_resource path="res://addons/fuzzy_display_tools/example_scene/CameraFollow.gd" type="Script" id=7] 10 | 11 | [node name="FuzzyDisplayToolsExample" type="Node2D"] 12 | 13 | [node name="CanvasLayer" type="CanvasLayer" parent="."] 14 | 15 | [node name="FuzzyViewportContainer" type="ViewportContainer" parent="CanvasLayer"] 16 | anchor_right = 1.0 17 | anchor_bottom = 1.0 18 | mouse_filter = 2 19 | stretch = true 20 | script = ExtResource( 2 ) 21 | __meta__ = { 22 | "_edit_use_anchors_": false 23 | } 24 | 25 | [node name="FuzzyViewport" type="Viewport" parent="CanvasLayer/FuzzyViewportContainer"] 26 | size = Vector2( 640, 360 ) 27 | transparent_bg = true 28 | handle_input_locally = false 29 | usage = 0 30 | render_target_update_mode = 3 31 | script = ExtResource( 3 ) 32 | 33 | [node name="ExampleUI" parent="CanvasLayer/FuzzyViewportContainer/FuzzyViewport" instance=ExtResource( 6 )] 34 | 35 | [node name="Level" type="Node2D" parent="."] 36 | 37 | [node name="icon" type="Sprite" parent="Level"] 38 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 39 | position = Vector2( 90.2015, 40.223 ) 40 | scale = Vector2( 2.20816, 1.35867 ) 41 | texture = ExtResource( 4 ) 42 | 43 | [node name="icon2" type="Sprite" parent="Level"] 44 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 45 | position = Vector2( 245.752, 88.5494 ) 46 | scale = Vector2( 2.20816, 1.35867 ) 47 | texture = ExtResource( 4 ) 48 | 49 | [node name="icon3" type="Sprite" parent="Level"] 50 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 51 | position = Vector2( 97.7524, 153.488 ) 52 | scale = Vector2( 2.20816, 1.35867 ) 53 | texture = ExtResource( 4 ) 54 | 55 | [node name="icon4" type="Sprite" parent="Level"] 56 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 57 | position = Vector2( 253.303, 179.161 ) 58 | scale = Vector2( 2.20816, 1.35867 ) 59 | texture = ExtResource( 4 ) 60 | 61 | [node name="icon5" type="Sprite" parent="Level"] 62 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 63 | position = Vector2( 355.997, 35.6924 ) 64 | scale = Vector2( 2.20816, 1.35867 ) 65 | texture = ExtResource( 4 ) 66 | 67 | [node name="icon6" type="Sprite" parent="Level"] 68 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 69 | position = Vector2( 511.547, 84.0188 ) 70 | scale = Vector2( 2.20816, 1.35867 ) 71 | texture = ExtResource( 4 ) 72 | 73 | [node name="icon7" type="Sprite" parent="Level"] 74 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 75 | position = Vector2( 363.547, 148.957 ) 76 | scale = Vector2( 2.20816, 1.35867 ) 77 | texture = ExtResource( 4 ) 78 | 79 | [node name="icon8" type="Sprite" parent="Level"] 80 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 81 | position = Vector2( 519.098, 174.631 ) 82 | scale = Vector2( 2.20816, 1.35867 ) 83 | texture = ExtResource( 4 ) 84 | 85 | [node name="icon9" type="Sprite" parent="Level"] 86 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 87 | position = Vector2( 301.629, 194.263 ) 88 | scale = Vector2( 2.20816, 1.35867 ) 89 | texture = ExtResource( 4 ) 90 | 91 | [node name="icon10" type="Sprite" parent="Level"] 92 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 93 | position = Vector2( 457.18, 242.59 ) 94 | scale = Vector2( 2.20816, 1.35867 ) 95 | texture = ExtResource( 4 ) 96 | 97 | [node name="icon11" type="Sprite" parent="Level"] 98 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 99 | position = Vector2( 309.18, 307.528 ) 100 | scale = Vector2( 2.20816, 1.35867 ) 101 | texture = ExtResource( 4 ) 102 | 103 | [node name="icon12" type="Sprite" parent="Level"] 104 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 105 | position = Vector2( 464.731, 333.202 ) 106 | scale = Vector2( 2.20816, 1.35867 ) 107 | texture = ExtResource( 4 ) 108 | 109 | [node name="icon13" type="Sprite" parent="Level"] 110 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 111 | position = Vector2( 72.0791, 209.365 ) 112 | scale = Vector2( 2.20816, 1.35867 ) 113 | texture = ExtResource( 4 ) 114 | 115 | [node name="icon14" type="Sprite" parent="Level"] 116 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 117 | position = Vector2( 227.63, 257.692 ) 118 | scale = Vector2( 2.20816, 1.35867 ) 119 | texture = ExtResource( 4 ) 120 | 121 | [node name="icon15" type="Sprite" parent="Level"] 122 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 123 | position = Vector2( 79.6301, 322.63 ) 124 | scale = Vector2( 2.20816, 1.35867 ) 125 | texture = ExtResource( 4 ) 126 | 127 | [node name="icon16" type="Sprite" parent="Level"] 128 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 129 | position = Vector2( 235.181, 348.304 ) 130 | scale = Vector2( 2.20816, 1.35867 ) 131 | texture = ExtResource( 4 ) 132 | 133 | [node name="icon17" type="Sprite" parent="Level"] 134 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 135 | position = Vector2( 417.915, 230.508 ) 136 | scale = Vector2( 2.20816, 1.35867 ) 137 | texture = ExtResource( 4 ) 138 | 139 | [node name="icon18" type="Sprite" parent="Level"] 140 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 141 | position = Vector2( 573.465, 278.834 ) 142 | scale = Vector2( 2.20816, 1.35867 ) 143 | texture = ExtResource( 4 ) 144 | 145 | [node name="icon19" type="Sprite" parent="Level"] 146 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 147 | position = Vector2( 425.466, 343.773 ) 148 | scale = Vector2( 2.20816, 1.35867 ) 149 | texture = ExtResource( 4 ) 150 | 151 | [node name="icon20" type="Sprite" parent="Level"] 152 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 153 | position = Vector2( 581.016, 369.446 ) 154 | scale = Vector2( 2.20816, 1.35867 ) 155 | texture = ExtResource( 4 ) 156 | 157 | [node name="icon21" type="Sprite" parent="Level"] 158 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 159 | position = Vector2( 487.384, 3.97827 ) 160 | scale = Vector2( 2.20816, 1.35867 ) 161 | texture = ExtResource( 4 ) 162 | 163 | [node name="icon22" type="Sprite" parent="Level"] 164 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 165 | position = Vector2( 644.444, 52.3046 ) 166 | scale = Vector2( 2.20816, 1.35867 ) 167 | texture = ExtResource( 4 ) 168 | 169 | [node name="icon23" type="Sprite" parent="Level"] 170 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 171 | position = Vector2( 494.935, 117.243 ) 172 | scale = Vector2( 2.20816, 1.35867 ) 173 | texture = ExtResource( 4 ) 174 | 175 | [node name="icon24" type="Sprite" parent="Level"] 176 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 177 | position = Vector2( 650.485, 142.917 ) 178 | scale = Vector2( 2.20816, 1.35867 ) 179 | texture = ExtResource( 4 ) 180 | 181 | [node name="icon25" type="Sprite" parent="Level"] 182 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 183 | position = Vector2( 198.936, -44.3481 ) 184 | scale = Vector2( 2.20816, 1.35867 ) 185 | texture = ExtResource( 4 ) 186 | 187 | [node name="icon26" type="Sprite" parent="Level"] 188 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 189 | position = Vector2( 354.486, 3.97826 ) 190 | scale = Vector2( 2.20816, 1.35867 ) 191 | texture = ExtResource( 4 ) 192 | 193 | [node name="icon27" type="Sprite" parent="Level"] 194 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 195 | position = Vector2( 206.487, 68.9168 ) 196 | scale = Vector2( 2.20816, 1.35867 ) 197 | texture = ExtResource( 4 ) 198 | 199 | [node name="icon28" type="Sprite" parent="Level"] 200 | modulate = Color( 0.0823529, 0.572549, 0.0235294, 1 ) 201 | position = Vector2( 362.037, 94.5902 ) 202 | scale = Vector2( 2.20816, 1.35867 ) 203 | texture = ExtResource( 4 ) 204 | 205 | [node name="Player1" type="Sprite" parent="." groups=[ 206 | "player", 207 | ]] 208 | position = Vector2( 140, 91.25 ) 209 | texture = ExtResource( 4 ) 210 | script = ExtResource( 5 ) 211 | up = "ui_up" 212 | down = "ui_down" 213 | left = "ui_left" 214 | right = "ui_right" 215 | 216 | [node name="Player2" type="Sprite" parent="." groups=[ 217 | "player", 218 | ]] 219 | modulate = Color( 1, 0, 0, 1 ) 220 | position = Vector2( 428.75, 168.75 ) 221 | texture = ExtResource( 4 ) 222 | script = ExtResource( 5 ) 223 | up = "p2_up" 224 | down = "p2_down" 225 | left = "p2_left" 226 | right = "p2_right" 227 | 228 | [node name="CameraFollow" type="Node2D" parent="."] 229 | script = ExtResource( 7 ) 230 | 231 | [node name="FuzzyCamera2D" type="Camera2D" parent="."] 232 | current = true 233 | script = ExtResource( 1 ) 234 | follow_target_path = NodePath("../CameraFollow") 235 | virtual_size = Vector2( 640, 360 ) 236 | -------------------------------------------------------------------------------- /addons/fuzzy_display_tools/example_scene/Player.gd: -------------------------------------------------------------------------------- 1 | extends Sprite 2 | 3 | const SPEED := 200.0 4 | 5 | export (String) var up := "" 6 | export (String) var down := "" 7 | export (String) var left := "" 8 | export (String) var right := "" 9 | 10 | 11 | func _process(delta): 12 | var dir := Vector2( 13 | Input.get_action_strength(right) - Input.get_action_strength(left), 14 | Input.get_action_strength(down) - Input.get_action_strength(up) 15 | ) 16 | global_position += dir * SPEED * delta 17 | -------------------------------------------------------------------------------- /addons/fuzzy_display_tools/nodes/FuzzyCamera2D.gd: -------------------------------------------------------------------------------- 1 | extends Camera2D 2 | class_name FuzzyCamera2D 3 | 4 | 5 | export (NodePath) var follow_target_path : NodePath 6 | var follow_target : Node2D 7 | 8 | 9 | export (bool) var use_virtual_size := true 10 | export (Vector2) var virtual_size := Vector2(320, 180) 11 | export (float, EXP, 0.01, 10.0) var zoom_f := 1.0 12 | 13 | 14 | func _ready(): 15 | add_to_group("camera") 16 | follow_target = get_node(follow_target_path) 17 | 18 | 19 | func _process(delta): 20 | 21 | if !is_instance_valid(follow_target): 22 | follow_target = get_node(follow_target_path) 23 | 24 | if follow_target: 25 | global_position = follow_target.position 26 | 27 | if use_virtual_size and !is_zero_approx(zoom_f): 28 | zoom = Vector2(virtual_size.round() / get_viewport().size) / zoom_f 29 | else: 30 | zoom = Vector2.ONE * zoom_f 31 | 32 | snap_and_update_scroll() 33 | 34 | 35 | # eliminates the jagging 36 | func snap_and_update_scroll() -> void: 37 | var old_pos := global_position 38 | global_position = global_position.snapped(zoom) 39 | 40 | force_update_scroll() 41 | global_position = old_pos 42 | 43 | 44 | func get_camera_size() -> Vector2: 45 | return get_viewport().size * zoom 46 | -------------------------------------------------------------------------------- /addons/fuzzy_display_tools/nodes/FuzzyViewport.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Viewport 3 | class_name FuzzyViewport 4 | 5 | # Hacky button 6 | export (bool) var reset_size_button := false setget reset_size_hack 7 | 8 | # Godot Engine bug? 9 | # The inspector UI does not update until another node is selected 10 | func reset_size_hack(value : bool) -> void: 11 | if value: 12 | size = get_main_viewport_size() 13 | print("%s size changed to: %s. Select another node to update Inspector." % [self.name, size]) 14 | reset_size_button = false 15 | 16 | 17 | func _init(): 18 | if Engine.editor_hint: 19 | transparent_bg = true 20 | handle_input_locally = false 21 | usage = USAGE_2D 22 | render_target_update_mode = UPDATE_ALWAYS 23 | size = get_main_viewport_size() 24 | 25 | 26 | func get_main_viewport_size() -> Vector2: 27 | return Vector2( 28 | ProjectSettings.get("display/window/size/width"), 29 | ProjectSettings.get("display/window/size/height") 30 | ) 31 | -------------------------------------------------------------------------------- /addons/fuzzy_display_tools/nodes/FuzzyViewportContainer.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends ViewportContainer 3 | class_name FuzzyViewportContainer 4 | 5 | 6 | func _init(): 7 | if Engine.editor_hint: 8 | stretch = true 9 | mouse_filter = MOUSE_FILTER_IGNORE 10 | 11 | 12 | func _ready(): 13 | if !Engine.editor_hint: 14 | FuzzyViewportScaler.connect("viewport_resized", self, "_on_viewport_resized") 15 | update_size() 16 | 17 | 18 | func _on_viewport_resized(vp_scale : float): 19 | update_size() 20 | 21 | 22 | func update_size() -> void: 23 | stretch_shrink = FuzzyViewportScaler.viewport_scale 24 | 25 | -------------------------------------------------------------------------------- /addons/fuzzy_display_tools/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Fuzzy Display Tools" 4 | description="A set of scripts to improve resolution handling, including: 5 | -Improved 2D camera with no lag/jitter. Logical resolution support. 6 | -Main viewport scaler singleton with support for those crisp, pixel perfect graphics! 7 | -Custom viewport and viewportcontainer for UI / Control nodes. 8 | 9 | Please check the README.md file for instructions." 10 | author="fossegutten" 11 | version="1.2" 12 | script="FuzzyDisplayTools.gd" 13 | -------------------------------------------------------------------------------- /addons/fuzzy_display_tools/singletons/FuzzyViewportScaler.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | signal viewport_resized(scale) 4 | 5 | ## TODO: 6 | # Check if removing black bars work 7 | # TODO add keep height / width modes 8 | 9 | ## HOWTO: 10 | # Add to AutoLoad in ProjectSettings (automatic if using the plugin) 11 | # Set base window size in ProjectSettings -> Window 12 | # Use stretch mode 'viewport' and aspect 'ignore' in ProjectSettings -> Window 13 | 14 | onready var viewport = get_viewport() 15 | onready var game_size : Vector2 = Vector2( 16 | ProjectSettings.get("display/window/size/width"), 17 | ProjectSettings.get("display/window/size/height") 18 | ) setget set_game_size, get_game_size 19 | 20 | enum ScaleMode { 21 | STRETCH, 22 | KEEP_ASPECT, 23 | } 24 | 25 | # Default values should same as default values in project manager ( first index / false ) 26 | export(ScaleMode) var scale_mode : int = 0 setget set_scale_mode, get_scale_mode 27 | export(bool) var pixel_perfect : bool = false setget set_pixel_perfect, is_pixel_perfect_enabled 28 | export(bool) var integer_scaling : bool = false setget set_integer_scaling, is_integer_scaling_enabled 29 | 30 | var viewport_scale : int = 1 setget ,get_viewport_scale 31 | 32 | 33 | func _ready(): 34 | get_tree().connect("screen_resized", self, "update_viewport_rect") 35 | 36 | if ProjectSettings.has_setting("display/fuzzy_display_tools/pixel_perfect"): 37 | pixel_perfect = ProjectSettings.get("display/fuzzy_display_tools/pixel_perfect") 38 | if ProjectSettings.has_setting("display/fuzzy_display_tools/scale_mode"): 39 | scale_mode = ProjectSettings.get("display/fuzzy_display_tools/scale_mode") 40 | if ProjectSettings.has_setting("display/fuzzy_display_tools/integer_scaling"): 41 | integer_scaling = ProjectSettings.get("display/fuzzy_display_tools/integer_scaling") 42 | 43 | update_viewport_rect() 44 | 45 | 46 | func set_game_size(value : Vector2) -> void: 47 | game_size = value 48 | update_viewport_rect() 49 | 50 | 51 | func set_pixel_perfect(value : bool) -> void: 52 | pixel_perfect = value 53 | update_viewport_rect() 54 | 55 | 56 | func set_integer_scaling(value : bool) -> void: 57 | integer_scaling = value 58 | update_viewport_rect() 59 | 60 | 61 | func set_scale_mode(value : int) -> void: 62 | scale_mode = value 63 | update_viewport_rect() 64 | 65 | 66 | func get_game_size() -> Vector2: 67 | return game_size 68 | 69 | 70 | func is_pixel_perfect_enabled() -> bool: 71 | return pixel_perfect 72 | 73 | 74 | func is_integer_scaling_enabled() -> bool: 75 | return integer_scaling 76 | 77 | 78 | func get_scale_mode() -> int: 79 | return scale_mode 80 | 81 | 82 | func get_viewport_scale() -> int: 83 | return viewport_scale 84 | 85 | 86 | func update_viewport_rect() -> void: 87 | if !is_inside_tree(): 88 | return 89 | 90 | var window_size : Vector2 = OS.get_window_size() 91 | var aspect_ratio : float = game_size.x / game_size.y 92 | 93 | if pixel_perfect: 94 | viewport_scale = 1 95 | else: 96 | viewport_scale = int(floor(max(1, min(window_size.x / game_size.x, window_size.y / game_size.y)))) 97 | 98 | # TODO calculate game width / height to fit screen, in keep height / width modes 99 | get_viewport().size = viewport_scale * game_size 100 | 101 | var target_rect : Rect2 = Rect2() 102 | 103 | 104 | match scale_mode: 105 | ScaleMode.STRETCH: 106 | target_rect.size = window_size 107 | ScaleMode.KEEP_ASPECT: 108 | var window_viewport_scale : Vector2 = window_size / viewport.size 109 | target_rect.size = game_size * viewport_scale * min(window_viewport_scale.x, window_viewport_scale.y) 110 | 111 | # clamp to game size 112 | target_rect.size.x = max(target_rect.size.x, game_size.x) 113 | target_rect.size.y = max(target_rect.size.y, game_size.y) 114 | 115 | if integer_scaling: 116 | # subtract modulo of game size 117 | target_rect.size.x -= fmod(target_rect.size.x, game_size.x) 118 | target_rect.size.y -= fmod(target_rect.size.y, game_size.y) 119 | 120 | # floor window pos, so we always go to the top left pixel, instead of jagging back and forth 121 | target_rect.position = (window_size / 2).floor() - (target_rect.size / 2) 122 | 123 | # attach the viewport to the rect we calculated 124 | viewport.set_attach_to_screen_rect(target_rect) 125 | 126 | update_black_bars(target_rect) 127 | 128 | emit_signal("viewport_resized", viewport_scale) 129 | 130 | 131 | func update_black_bars(target_rect : Rect2) -> void: 132 | # BUG(?) in Godot 3.x 133 | # because godot doesnt clear the render buffer when resizing the viewport rect, we add black bars 134 | var window_size := OS.get_window_size() 135 | 136 | var black_bars : Vector2 = (window_size - target_rect.size) / 2 137 | var odd_pixel : Vector2 = Vector2(int(window_size.x) % 2, int(window_size.y) % 2) 138 | 139 | if window_size.x < viewport.size.x: 140 | black_bars.x = 0 141 | odd_pixel.x = 0 142 | if window_size.y < viewport.size.y: 143 | black_bars.y = 0 144 | odd_pixel.y = 0 145 | 146 | # add one pixel to right and bottom black bars, when we have odd number sized window 147 | VisualServer.black_bars_set_margins( 148 | black_bars.x, 149 | black_bars.y, 150 | black_bars.x + odd_pixel.x, 151 | black_bars.y + odd_pixel.y 152 | ) 153 | -------------------------------------------------------------------------------- /default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | [resource] 6 | background_mode = 2 7 | background_sky = SubResource( 1 ) 8 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossegutten/FuzzyDisplayTools/c03ea55f01dec1b8b440709dd03bbb3250cdfb73/icon.png -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://icon.png" 13 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossegutten/FuzzyDisplayTools/c03ea55f01dec1b8b440709dd03bbb3250cdfb73/logo.png -------------------------------------------------------------------------------- /logo.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/logo.png-cca8726399059c8d4f806e28e356b14d.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://logo.png" 13 | dest_files=[ "res://.import/logo.png-cca8726399059c8d4f806e28e356b14d.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=4 10 | 11 | _global_script_classes=[ { 12 | "base": "Camera2D", 13 | "class": "FuzzyCamera2D", 14 | "language": "GDScript", 15 | "path": "res://addons/fuzzy_display_tools/nodes/FuzzyCamera2D.gd" 16 | }, { 17 | "base": "Viewport", 18 | "class": "FuzzyViewport", 19 | "language": "GDScript", 20 | "path": "res://addons/fuzzy_display_tools/nodes/FuzzyViewport.gd" 21 | }, { 22 | "base": "ViewportContainer", 23 | "class": "FuzzyViewportContainer", 24 | "language": "GDScript", 25 | "path": "res://addons/fuzzy_display_tools/nodes/FuzzyViewportContainer.gd" 26 | } ] 27 | _global_script_class_icons={ 28 | "FuzzyCamera2D": "", 29 | "FuzzyViewport": "", 30 | "FuzzyViewportContainer": "" 31 | } 32 | 33 | [application] 34 | 35 | config/name="FuzzyDisplayTools" 36 | run/main_scene="res://addons/fuzzy_display_tools/example_scene/FuzzyDisplayToolsExample.tscn" 37 | config/icon="res://logo.png" 38 | 39 | [autoload] 40 | 41 | FuzzyViewportScaler="*res://addons/fuzzy_display_tools/singletons/FuzzyViewportScaler.gd" 42 | 43 | [display] 44 | 45 | window/size/width=640 46 | window/size/height=360 47 | window/dpi/allow_hidpi=true 48 | window/stretch/mode="viewport" 49 | fuzzy_display_tools/scale_mode=1 50 | 51 | [editor_plugins] 52 | 53 | enabled=PoolStringArray( "fuzzy_display_tools" ) 54 | 55 | [input] 56 | 57 | p2_left={ 58 | "deadzone": 0.5, 59 | "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":65,"unicode":0,"echo":false,"script":null) 60 | ] 61 | } 62 | p2_right={ 63 | "deadzone": 0.5, 64 | "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":68,"unicode":0,"echo":false,"script":null) 65 | ] 66 | } 67 | p2_up={ 68 | "deadzone": 0.5, 69 | "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":87,"unicode":0,"echo":false,"script":null) 70 | ] 71 | } 72 | p2_down={ 73 | "deadzone": 0.5, 74 | "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":83,"unicode":0,"echo":false,"script":null) 75 | ] 76 | } 77 | 78 | [rendering] 79 | 80 | environment/default_environment="res://default_env.tres" 81 | quality/dynamic_fonts/use_oversampling=false 82 | --------------------------------------------------------------------------------