Add resolution-independent pixelation
This commit is contained in:
parent
46175ee405
commit
6f51f91324
10 changed files with 220 additions and 33 deletions
26
main/main.gd
26
main/main.gd
|
|
@ -30,6 +30,9 @@ const UIPixelationPresenterType = preload(
|
||||||
const PixelationResetOverlayType = preload(
|
const PixelationResetOverlayType = preload(
|
||||||
"res://ui/pixelation_reset_overlay.gd"
|
"res://ui/pixelation_reset_overlay.gd"
|
||||||
)
|
)
|
||||||
|
const WorldPixelationPostprocessType = preload(
|
||||||
|
"res://main/world_pixelation_postprocess.gd"
|
||||||
|
)
|
||||||
|
|
||||||
const TITLE_MUSIC_SILENCE_DB: float = -80.0
|
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 = (
|
@onready var _pixelation_reset: PixelationResetOverlayType = (
|
||||||
%PixelationResetOverlay
|
%PixelationResetOverlay
|
||||||
)
|
)
|
||||||
|
@onready var _world_pixelation: WorldPixelationPostprocessType = (
|
||||||
|
%WorldPixelationPostprocess
|
||||||
|
)
|
||||||
|
|
||||||
var _gameplay_started: bool = false
|
var _gameplay_started: bool = false
|
||||||
var _shop_interaction: FishingShopInteractionType
|
var _shop_interaction: FishingShopInteractionType
|
||||||
|
|
@ -241,14 +247,7 @@ func _process(_delta: float) -> void:
|
||||||
func _apply_runtime_settings(settings: PlayerSettingsType) -> void:
|
func _apply_runtime_settings(settings: PlayerSettingsType) -> void:
|
||||||
if settings == null:
|
if settings == null:
|
||||||
return
|
return
|
||||||
var root_viewport: Viewport = get_viewport()
|
_apply_world_pixelation(settings.world_pixel_size)
|
||||||
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
|
|
||||||
_ui_pixelation.set_pixel_size(settings.ui_pixel_size)
|
_ui_pixelation.set_pixel_size(settings.ui_pixel_size)
|
||||||
_game_ui.get_title_screen().set_world_pixelation(
|
_game_ui.get_title_screen().set_world_pixelation(
|
||||||
settings.world_pixel_size
|
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(
|
func _on_effective_ui_pixel_size_changed(
|
||||||
_requested_pixel_size: int,
|
_requested_pixel_size: int,
|
||||||
effective_pixel_size: int,
|
effective_pixel_size: int,
|
||||||
|
|
@ -281,6 +290,7 @@ func _reset_pixelation() -> void:
|
||||||
|
|
||||||
func _set_gameplay_active(active: bool) -> void:
|
func _set_gameplay_active(active: bool) -> void:
|
||||||
_gameplay_started = active
|
_gameplay_started = active
|
||||||
|
_world_pixelation.set_gameplay_active(active)
|
||||||
_ui_pixelation.set_gameplay_active(active)
|
_ui_pixelation.set_gameplay_active(active)
|
||||||
if not active:
|
if not active:
|
||||||
_player.item_effects.reset_all()
|
_player.item_effects.reset_all()
|
||||||
|
|
|
||||||
|
|
@ -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://world/test_world.tscn" id="1_world"]
|
||||||
[ext_resource type="PackedScene" path="res://player/player.tscn" id="2_player"]
|
[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="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="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://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"]
|
[node name="Main" type="Node3D"]
|
||||||
script = ExtResource("3_main")
|
script = ExtResource("3_main")
|
||||||
|
|
@ -53,6 +54,9 @@ stream = ExtResource("13_title_music")
|
||||||
volume_db = -80.0
|
volume_db = -80.0
|
||||||
bus = &"Music"
|
bus = &"Music"
|
||||||
|
|
||||||
|
[node name="WorldPixelationPostprocess" parent="." instance=ExtResource("16_world_pixelation")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
[node name="UIPresentation" type="SubViewportContainer" parent="."]
|
[node name="UIPresentation" type="SubViewportContainer" parent="."]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
texture_filter = 1
|
texture_filter = 1
|
||||||
|
|
|
||||||
51
main/world_pixelation_postprocess.gd
Normal file
51
main/world_pixelation_postprocess.gd
Normal file
|
|
@ -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())
|
||||||
|
)
|
||||||
1
main/world_pixelation_postprocess.gd.uid
Normal file
1
main/world_pixelation_postprocess.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://c3n47x8egoc8x
|
||||||
15
main/world_pixelation_postprocess.gdshader
Normal file
15
main/world_pixelation_postprocess.gdshader
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
1
main/world_pixelation_postprocess.gdshader.uid
Normal file
1
main/world_pixelation_postprocess.gdshader.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://sq224d8sn1b4
|
||||||
21
main/world_pixelation_postprocess.tscn
Normal file
21
main/world_pixelation_postprocess.tscn
Normal file
|
|
@ -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
|
||||||
|
|
@ -13,8 +13,11 @@ const DEFAULT_WORLD_PIXEL_SIZE: int = 3
|
||||||
const MIN_UI_PIXEL_SIZE: int = 1
|
const MIN_UI_PIXEL_SIZE: int = 1
|
||||||
const MAX_UI_PIXEL_SIZE: int = 5
|
const MAX_UI_PIXEL_SIZE: int = 5
|
||||||
const DEFAULT_UI_PIXEL_SIZE: int = 3
|
const DEFAULT_UI_PIXEL_SIZE: int = 3
|
||||||
const WORLD_RENDER_SCALES: Array[float] = [1.0, 0.9, 0.8, 0.65, 0.45]
|
const COMPACT_DISPLAY_HEIGHT: int = 560
|
||||||
const UI_RENDER_SCALES: Array[float] = [1.0, 0.975, 0.95, 0.925, 0.9]
|
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 var auto_click_enabled: bool = false
|
||||||
@export_range(0.10, 0.50, 0.01) var auto_click_interval: float = 0.20
|
@export_range(0.10, 0.50, 0.01) var auto_click_interval: float = 0.20
|
||||||
|
|
@ -55,19 +58,89 @@ func copy() -> PlayerSettings:
|
||||||
return result
|
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(
|
var index: int = clampi(
|
||||||
pixel_size,
|
pixel_size,
|
||||||
MIN_WORLD_PIXEL_SIZE,
|
MIN_WORLD_PIXEL_SIZE,
|
||||||
MAX_WORLD_PIXEL_SIZE
|
MAX_WORLD_PIXEL_SIZE
|
||||||
) - 1
|
) - 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(
|
var index: int = clampi(
|
||||||
pixel_size,
|
pixel_size,
|
||||||
MIN_UI_PIXEL_SIZE,
|
MIN_UI_PIXEL_SIZE,
|
||||||
MAX_UI_PIXEL_SIZE
|
MAX_UI_PIXEL_SIZE
|
||||||
) - 1
|
) - 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
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -379,20 +379,25 @@ func set_world_pixelation(pixel_size: int) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _update_world_preview_resolution() -> void:
|
func _update_world_preview_resolution() -> void:
|
||||||
var render_scale: float = PlayerSettings.get_world_render_scale(
|
var displayed_size := Vector2i(
|
||||||
_world_pixel_size
|
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
|
var shader_material := _background.material as ShaderMaterial
|
||||||
if shader_material != null:
|
if shader_material != null:
|
||||||
shader_material.set_shader_parameter(
|
shader_material.set_shader_parameter(
|
||||||
"virtual_pixel_density",
|
"virtual_pixel_density",
|
||||||
Vector2(
|
Vector2(grid_size)
|
||||||
maxf(1.0, size.x * render_scale),
|
|
||||||
maxf(1.0, size.y * render_scale)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
var logo_material := _title_logo.material as ShaderMaterial
|
var logo_material := _title_logo.material as ShaderMaterial
|
||||||
if logo_material != null:
|
if logo_material != null:
|
||||||
|
var render_scale: float = (
|
||||||
|
float(grid_size.y) / float(displayed_size.y)
|
||||||
|
)
|
||||||
var effect_scale: float = 1.0 / render_scale
|
var effect_scale: float = 1.0 / render_scale
|
||||||
logo_material.set_shader_parameter(
|
logo_material.set_shader_parameter(
|
||||||
"horizontal_displacement_pixels",
|
"horizontal_displacement_pixels",
|
||||||
|
|
@ -405,9 +410,15 @@ func _update_world_preview_resolution() -> void:
|
||||||
|
|
||||||
|
|
||||||
func _snap_world_preview(value: Vector2) -> Vector2:
|
func _snap_world_preview(value: Vector2) -> Vector2:
|
||||||
var render_scale: float = PlayerSettings.get_world_render_scale(
|
var displayed_size := Vector2i(
|
||||||
_world_pixel_size
|
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
|
var step: float = 1.0 / render_scale
|
||||||
return Vector2(
|
return Vector2(
|
||||||
roundf(value.x / step) * step,
|
roundf(value.x / step) * step,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
class_name UIPixelationPresenter
|
class_name UIPixelationPresenter
|
||||||
extends SubViewportContainer
|
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(
|
signal effective_pixel_size_changed(
|
||||||
requested_pixel_size: int,
|
requested_pixel_size: int,
|
||||||
|
|
@ -70,12 +70,13 @@ func _resize_presentation() -> void:
|
||||||
return
|
return
|
||||||
var previous_effective_size: int = _effective_pixel_size
|
var previous_effective_size: int = _effective_pixel_size
|
||||||
_effective_pixel_size = _resolve_effective_pixel_size(root_size)
|
_effective_pixel_size = _resolve_effective_pixel_size(root_size)
|
||||||
var render_scale: float = PlayerSettings.get_ui_render_scale(
|
var viewport_size: Vector2i = PlayerSettings.get_ui_render_size(
|
||||||
_effective_pixel_size
|
_effective_pixel_size,
|
||||||
|
root_size
|
||||||
)
|
)
|
||||||
var viewport_size := Vector2i(
|
var render_scale: float = minf(
|
||||||
ceili(float(root_size.x) * render_scale),
|
1.0,
|
||||||
ceili(float(root_size.y) * render_scale)
|
float(viewport_size.y) / float(root_size.y)
|
||||||
)
|
)
|
||||||
var presentation_size: Vector2 = Vector2(viewport_size) / render_scale
|
var presentation_size: Vector2 = Vector2(viewport_size) / render_scale
|
||||||
set_anchors_preset(Control.PRESET_TOP_LEFT)
|
set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||||
|
|
@ -98,10 +99,9 @@ func _resize_presentation() -> void:
|
||||||
|
|
||||||
func _resolve_effective_pixel_size(root_size: Vector2i) -> int:
|
func _resolve_effective_pixel_size(root_size: Vector2i) -> int:
|
||||||
for candidate: int in range(_requested_pixel_size, 0, -1):
|
for candidate: int in range(_requested_pixel_size, 0, -1):
|
||||||
var render_scale: float = PlayerSettings.get_ui_render_scale(candidate)
|
var candidate_size: Vector2i = PlayerSettings.get_ui_render_size(
|
||||||
var candidate_size := Vector2i(
|
candidate,
|
||||||
ceili(float(root_size.x) * render_scale),
|
root_size
|
||||||
ceili(float(root_size.y) * render_scale)
|
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
candidate_size.x >= MIN_UI_VIEWPORT_SIZE.x
|
candidate_size.x >= MIN_UI_VIEWPORT_SIZE.x
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue