diff --git a/main/main.gd b/main/main.gd index 21e621f..51e645b 100644 --- a/main/main.gd +++ b/main/main.gd @@ -30,6 +30,9 @@ const UIPixelationPresenterType = preload( const PixelationResetOverlayType = preload( "res://ui/pixelation_reset_overlay.gd" ) +const WorldPixelationPostprocessType = preload( + "res://main/world_pixelation_postprocess.gd" +) const TITLE_MUSIC_SILENCE_DB: float = -80.0 @@ -53,6 +56,9 @@ const TITLE_MUSIC_SILENCE_DB: float = -80.0 @onready var _pixelation_reset: PixelationResetOverlayType = ( %PixelationResetOverlay ) +@onready var _world_pixelation: WorldPixelationPostprocessType = ( + %WorldPixelationPostprocess +) var _gameplay_started: bool = false var _shop_interaction: FishingShopInteractionType @@ -241,14 +247,7 @@ func _process(_delta: float) -> void: func _apply_runtime_settings(settings: PlayerSettingsType) -> void: if settings == null: return - var root_viewport: Viewport = get_viewport() - root_viewport.scaling_3d_mode = Viewport.SCALING_3D_MODE_NEAREST - root_viewport.scaling_3d_scale = PlayerSettingsType.get_world_render_scale( - settings.world_pixel_size - ) - root_viewport.msaa_3d = Viewport.MSAA_DISABLED - root_viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED - root_viewport.use_taa = false + _apply_world_pixelation(settings.world_pixel_size) _ui_pixelation.set_pixel_size(settings.ui_pixel_size) _game_ui.get_title_screen().set_world_pixelation( settings.world_pixel_size @@ -264,6 +263,16 @@ func _apply_runtime_settings(settings: PlayerSettingsType) -> void: ) +func _apply_world_pixelation(pixel_size: int) -> void: + var root_viewport: Viewport = get_viewport() + root_viewport.scaling_3d_mode = Viewport.SCALING_3D_MODE_NEAREST + root_viewport.scaling_3d_scale = 1.0 + root_viewport.msaa_3d = Viewport.MSAA_DISABLED + root_viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED + root_viewport.use_taa = false + _world_pixelation.set_pixel_size(pixel_size) + + func _on_effective_ui_pixel_size_changed( _requested_pixel_size: int, effective_pixel_size: int, @@ -281,6 +290,7 @@ func _reset_pixelation() -> void: func _set_gameplay_active(active: bool) -> void: _gameplay_started = active + _world_pixelation.set_gameplay_active(active) _ui_pixelation.set_gameplay_active(active) if not active: _player.item_effects.reset_all() diff --git a/main/main.tscn b/main/main.tscn index 9729966..192178b 100644 --- a/main/main.tscn +++ b/main/main.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=16 format=3] +[gd_scene load_steps=17 format=3] [ext_resource type="PackedScene" path="res://world/test_world.tscn" id="1_world"] [ext_resource type="PackedScene" path="res://player/player.tscn" id="2_player"] @@ -15,6 +15,7 @@ [ext_resource type="AudioStream" path="res://audio/music/title/as_in_four_wolves.ogg" id="13_title_music"] [ext_resource type="Script" path="res://ui/ui_pixelation_presenter.gd" id="14_pixelation"] [ext_resource type="PackedScene" path="res://ui/pixelation_reset_overlay.tscn" id="15_reset"] +[ext_resource type="PackedScene" path="res://main/world_pixelation_postprocess.tscn" id="16_world_pixelation"] [node name="Main" type="Node3D"] script = ExtResource("3_main") @@ -53,6 +54,9 @@ stream = ExtResource("13_title_music") volume_db = -80.0 bus = &"Music" +[node name="WorldPixelationPostprocess" parent="." instance=ExtResource("16_world_pixelation")] +unique_name_in_owner = true + [node name="UIPresentation" type="SubViewportContainer" parent="."] unique_name_in_owner = true texture_filter = 1 diff --git a/main/world_pixelation_postprocess.gd b/main/world_pixelation_postprocess.gd new file mode 100644 index 0000000..303fa3e --- /dev/null +++ b/main/world_pixelation_postprocess.gd @@ -0,0 +1,51 @@ +class_name WorldPixelationPostprocess +extends CanvasLayer + +@onready var _screen_grid: ColorRect = %ScreenGrid + +var _pixel_size: int = PlayerSettings.DEFAULT_WORLD_PIXEL_SIZE +var _gameplay_active: bool = false + + +func _ready() -> void: + get_viewport().size_changed.connect(_refresh_grid) + _refresh_grid() + + +func set_pixel_size(pixel_size: int) -> void: + _pixel_size = clampi( + pixel_size, + PlayerSettings.MIN_WORLD_PIXEL_SIZE, + PlayerSettings.MAX_WORLD_PIXEL_SIZE + ) + _refresh_grid() + + +func set_gameplay_active(active: bool) -> void: + _gameplay_active = active + _refresh_grid() + + +func get_grid_size() -> Vector2i: + return PlayerSettings.get_world_grid_size( + _pixel_size, + get_window().size + ) + + +func _refresh_grid() -> void: + if not is_node_ready(): + return + var effect_enabled: bool = ( + _gameplay_active + and _pixel_size != PlayerSettings.MIN_WORLD_PIXEL_SIZE + ) + _screen_grid.visible = effect_enabled + if not effect_enabled: + return + var shader_material := _screen_grid.material as ShaderMaterial + if shader_material != null: + shader_material.set_shader_parameter( + "logical_grid_size", + Vector2(get_grid_size()) + ) diff --git a/main/world_pixelation_postprocess.gd.uid b/main/world_pixelation_postprocess.gd.uid new file mode 100644 index 0000000..b77b05c --- /dev/null +++ b/main/world_pixelation_postprocess.gd.uid @@ -0,0 +1 @@ +uid://c3n47x8egoc8x diff --git a/main/world_pixelation_postprocess.gdshader b/main/world_pixelation_postprocess.gdshader new file mode 100644 index 0000000..4018a3f --- /dev/null +++ b/main/world_pixelation_postprocess.gdshader @@ -0,0 +1,15 @@ +shader_type canvas_item; +render_mode unshaded; + +uniform sampler2D screen_texture + : hint_screen_texture, filter_nearest, repeat_disable; +uniform vec2 logical_grid_size = vec2(640.0, 360.0); + + +void fragment() { + vec2 safe_grid = max(logical_grid_size, vec2(1.0)); + vec2 snapped_uv = ( + floor(SCREEN_UV * safe_grid) + vec2(0.5) + ) / safe_grid; + COLOR = texture(screen_texture, snapped_uv); +} diff --git a/main/world_pixelation_postprocess.gdshader.uid b/main/world_pixelation_postprocess.gdshader.uid new file mode 100644 index 0000000..0b61936 --- /dev/null +++ b/main/world_pixelation_postprocess.gdshader.uid @@ -0,0 +1 @@ +uid://sq224d8sn1b4 diff --git a/main/world_pixelation_postprocess.tscn b/main/world_pixelation_postprocess.tscn new file mode 100644 index 0000000..b6f952b --- /dev/null +++ b/main/world_pixelation_postprocess.tscn @@ -0,0 +1,21 @@ +[gd_scene load_steps=3 format=3] + +[ext_resource type="Script" path="res://main/world_pixelation_postprocess.gd" id="1_script"] +[ext_resource type="Shader" path="res://main/world_pixelation_postprocess.gdshader" id="2_shader"] + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_world_pixelation"] +shader = ExtResource("2_shader") + +[node name="WorldPixelationPostprocess" type="CanvasLayer"] +layer = -1 +script = ExtResource("1_script") + +[node name="ScreenGrid" type="ColorRect" parent="."] +unique_name_in_owner = true +material = SubResource("ShaderMaterial_world_pixelation") +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 diff --git a/settings/player_settings.gd b/settings/player_settings.gd index 0d3657e..44c00a3 100644 --- a/settings/player_settings.gd +++ b/settings/player_settings.gd @@ -13,8 +13,11 @@ const DEFAULT_WORLD_PIXEL_SIZE: int = 3 const MIN_UI_PIXEL_SIZE: int = 1 const MAX_UI_PIXEL_SIZE: int = 5 const DEFAULT_UI_PIXEL_SIZE: int = 3 -const WORLD_RENDER_SCALES: Array[float] = [1.0, 0.9, 0.8, 0.65, 0.45] -const UI_RENDER_SCALES: Array[float] = [1.0, 0.975, 0.95, 0.925, 0.9] +const COMPACT_DISPLAY_HEIGHT: int = 560 +const WORLD_DESKTOP_GRID_HEIGHTS: Array[int] = [0, 540, 360, 216, 96] +const WORLD_COMPACT_GRID_HEIGHTS: Array[int] = [0, 360, 240, 144, 72] +const UI_DESKTOP_RENDER_HEIGHTS: Array[int] = [0, 612, 504, 396, 288] +const UI_COMPACT_RENDER_HEIGHTS: Array[int] = [0, 408, 336, 264, 192] @export var auto_click_enabled: bool = false @export_range(0.10, 0.50, 0.01) var auto_click_interval: float = 0.20 @@ -55,19 +58,89 @@ func copy() -> PlayerSettings: return result -static func get_world_render_scale(pixel_size: int) -> float: +static func get_world_grid_height( + pixel_size: int, + displayed_height: int, +) -> int: var index: int = clampi( pixel_size, MIN_WORLD_PIXEL_SIZE, MAX_WORLD_PIXEL_SIZE ) - 1 - return WORLD_RENDER_SCALES[index] + if index == 0: + return maxi(1, displayed_height) + var targets: Array[int] = ( + WORLD_COMPACT_GRID_HEIGHTS + if displayed_height < COMPACT_DISPLAY_HEIGHT + else WORLD_DESKTOP_GRID_HEIGHTS + ) + return mini(maxi(1, displayed_height), targets[index]) -static func get_ui_render_scale(pixel_size: int) -> float: +static func get_world_grid_size( + pixel_size: int, + displayed_size: Vector2i, +) -> Vector2i: + var safe_size := Vector2i( + maxi(1, displayed_size.x), + maxi(1, displayed_size.y) + ) + var target_height: int = get_world_grid_height( + pixel_size, + safe_size.y + ) + return Vector2i( + maxi( + 1, + roundi( + float(target_height) + * float(safe_size.x) + / float(safe_size.y) + ) + ), + target_height + ) + + +static func get_ui_render_height( + pixel_size: int, + displayed_height: int, +) -> int: var index: int = clampi( pixel_size, MIN_UI_PIXEL_SIZE, MAX_UI_PIXEL_SIZE ) - 1 - return UI_RENDER_SCALES[index] + if index == 0: + return maxi(1, displayed_height) + var targets: Array[int] = ( + UI_COMPACT_RENDER_HEIGHTS + if displayed_height < COMPACT_DISPLAY_HEIGHT + else UI_DESKTOP_RENDER_HEIGHTS + ) + return mini(maxi(1, displayed_height), targets[index]) + + +static func get_ui_render_size( + pixel_size: int, + displayed_size: Vector2i, +) -> Vector2i: + var safe_size := Vector2i( + maxi(1, displayed_size.x), + maxi(1, displayed_size.y) + ) + var target_height: int = get_ui_render_height( + pixel_size, + safe_size.y + ) + return Vector2i( + maxi( + 1, + roundi( + float(target_height) + * float(safe_size.x) + / float(safe_size.y) + ) + ), + target_height + ) diff --git a/ui/title_screen.gd b/ui/title_screen.gd index c3552fa..aab4921 100644 --- a/ui/title_screen.gd +++ b/ui/title_screen.gd @@ -379,20 +379,25 @@ func set_world_pixelation(pixel_size: int) -> void: func _update_world_preview_resolution() -> void: - var render_scale: float = PlayerSettings.get_world_render_scale( - _world_pixel_size + var displayed_size := Vector2i( + maxi(1, roundi(size.x)), + maxi(1, roundi(size.y)) + ) + var grid_size: Vector2i = PlayerSettings.get_world_grid_size( + _world_pixel_size, + displayed_size ) var shader_material := _background.material as ShaderMaterial if shader_material != null: shader_material.set_shader_parameter( "virtual_pixel_density", - Vector2( - maxf(1.0, size.x * render_scale), - maxf(1.0, size.y * render_scale) - ) + Vector2(grid_size) ) var logo_material := _title_logo.material as ShaderMaterial if logo_material != null: + var render_scale: float = ( + float(grid_size.y) / float(displayed_size.y) + ) var effect_scale: float = 1.0 / render_scale logo_material.set_shader_parameter( "horizontal_displacement_pixels", @@ -405,9 +410,15 @@ func _update_world_preview_resolution() -> void: func _snap_world_preview(value: Vector2) -> Vector2: - var render_scale: float = PlayerSettings.get_world_render_scale( - _world_pixel_size + var displayed_size := Vector2i( + maxi(1, roundi(size.x)), + maxi(1, roundi(size.y)) ) + var grid_size: Vector2i = PlayerSettings.get_world_grid_size( + _world_pixel_size, + displayed_size + ) + var render_scale: float = float(grid_size.y) / float(displayed_size.y) var step: float = 1.0 / render_scale return Vector2( roundf(value.x / step) * step, diff --git a/ui/ui_pixelation_presenter.gd b/ui/ui_pixelation_presenter.gd index ca41c15..476f3a8 100644 --- a/ui/ui_pixelation_presenter.gd +++ b/ui/ui_pixelation_presenter.gd @@ -1,7 +1,7 @@ class_name UIPixelationPresenter extends SubViewportContainer -const MIN_UI_VIEWPORT_SIZE: Vector2i = Vector2i(320, 180) +const MIN_UI_VIEWPORT_SIZE: Vector2i = Vector2i(256, 180) signal effective_pixel_size_changed( requested_pixel_size: int, @@ -70,12 +70,13 @@ func _resize_presentation() -> void: return var previous_effective_size: int = _effective_pixel_size _effective_pixel_size = _resolve_effective_pixel_size(root_size) - var render_scale: float = PlayerSettings.get_ui_render_scale( - _effective_pixel_size + var viewport_size: Vector2i = PlayerSettings.get_ui_render_size( + _effective_pixel_size, + root_size ) - var viewport_size := Vector2i( - ceili(float(root_size.x) * render_scale), - ceili(float(root_size.y) * render_scale) + var render_scale: float = minf( + 1.0, + float(viewport_size.y) / float(root_size.y) ) var presentation_size: Vector2 = Vector2(viewport_size) / render_scale set_anchors_preset(Control.PRESET_TOP_LEFT) @@ -98,10 +99,9 @@ func _resize_presentation() -> void: func _resolve_effective_pixel_size(root_size: Vector2i) -> int: for candidate: int in range(_requested_pixel_size, 0, -1): - var render_scale: float = PlayerSettings.get_ui_render_scale(candidate) - var candidate_size := Vector2i( - ceili(float(root_size.x) * render_scale), - ceili(float(root_size.y) * render_scale) + var candidate_size: Vector2i = PlayerSettings.get_ui_render_size( + candidate, + root_size ) if ( candidate_size.x >= MIN_UI_VIEWPORT_SIZE.x