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(
|
||||
"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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
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 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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue