Compare commits

...

2 commits

Author SHA1 Message Date
053cfe037d Extract reusable bubble menu components 2026-07-27 14:42:57 -04:00
c7504d2585 Add independent pixelation controls 2026-07-27 14:39:37 -04:00
30 changed files with 1381 additions and 176 deletions

View file

@ -24,6 +24,12 @@ const FishingShopType = preload("res://ui/fishing_shop.gd")
const FishingShopInteractionType = preload( const FishingShopInteractionType = preload(
"res://world/fishing_shop_interaction.gd" "res://world/fishing_shop_interaction.gd"
) )
const UIPixelationPresenterType = preload(
"res://ui/ui_pixelation_presenter.gd"
)
const PixelationResetOverlayType = preload(
"res://ui/pixelation_reset_overlay.gd"
)
const TITLE_MUSIC_SILENCE_DB: float = -80.0 const TITLE_MUSIC_SILENCE_DB: float = -80.0
@ -43,6 +49,10 @@ const TITLE_MUSIC_SILENCE_DB: float = -80.0
@onready var _save_manager: PlayerSaveManagerType = %PlayerSaveManager @onready var _save_manager: PlayerSaveManagerType = %PlayerSaveManager
@onready var _settings_manager: PlayerSettingsManagerType = %PlayerSettingsManager @onready var _settings_manager: PlayerSettingsManagerType = %PlayerSettingsManager
@onready var _title_music: AudioStreamPlayer = %TitleMusic @onready var _title_music: AudioStreamPlayer = %TitleMusic
@onready var _ui_pixelation: UIPixelationPresenterType = %UIPresentation
@onready var _pixelation_reset: PixelationResetOverlayType = (
%PixelationResetOverlay
)
var _gameplay_started: bool = false var _gameplay_started: bool = false
var _shop_interaction: FishingShopInteractionType var _shop_interaction: FishingShopInteractionType
@ -119,6 +129,22 @@ func _ready() -> void:
_apply_runtime_settings _apply_runtime_settings
): ):
_settings_manager.settings_changed.connect(_apply_runtime_settings) _settings_manager.settings_changed.connect(_apply_runtime_settings)
_ui_pixelation.effective_pixel_size_changed.connect(
_on_effective_ui_pixel_size_changed
)
_game_ui.pixelation_settings_visibility_changed.connect(
_pixelation_reset.set_settings_open
)
_game_ui.crisp_reset_focus_requested.connect(
_pixelation_reset.focus_reset_button
)
_game_ui.interactive_pointer_ui_changed.connect(
_ui_pixelation.set_interactive_ui_open
)
_pixelation_reset.return_to_settings_requested.connect(
_game_ui.focus_open_settings_back_button
)
_pixelation_reset.reset_requested.connect(_reset_pixelation)
_settings_manager.load_settings() _settings_manager.load_settings()
_apply_runtime_settings(_settings_manager.current_settings) _apply_runtime_settings(_settings_manager.current_settings)
_set_gameplay_active(false) _set_gameplay_active(false)
@ -215,6 +241,18 @@ 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()
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)
_game_ui.get_title_screen().set_world_pixelation(
settings.world_pixel_size
)
_player.apply_camera_settings( _player.apply_camera_settings(
settings.mouse_camera_sensitivity, settings.mouse_camera_sensitivity,
settings.controller_camera_sensitivity, settings.controller_camera_sensitivity,
@ -226,8 +264,24 @@ func _apply_runtime_settings(settings: PlayerSettingsType) -> void:
) )
func _on_effective_ui_pixel_size_changed(
_requested_pixel_size: int,
effective_pixel_size: int,
) -> void:
_game_ui.set_effective_ui_pixel_size(effective_pixel_size)
func _reset_pixelation() -> void:
if _settings_manager.apply_pixelation(
PlayerSettingsType.DEFAULT_WORLD_PIXEL_SIZE,
PlayerSettingsType.DEFAULT_UI_PIXEL_SIZE
):
_game_ui.refresh_open_settings_panel()
func _set_gameplay_active(active: bool) -> void: func _set_gameplay_active(active: bool) -> void:
_gameplay_started = active _gameplay_started = active
_ui_pixelation.set_gameplay_active(active)
if not active: if not active:
_player.item_effects.reset_all() _player.item_effects.reset_all()
_player.set_movement_enabled(active) _player.set_movement_enabled(active)

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=14 format=3] [gd_scene load_steps=16 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"]
@ -13,6 +13,8 @@
[ext_resource type="Resource" path="res://items/catalog/item_catalog.tres" id="11_items"] [ext_resource type="Resource" path="res://items/catalog/item_catalog.tres" id="11_items"]
[ext_resource type="Resource" path="res://economy/buyers/main_fishing_shop.tres" id="12_main_shop"] [ext_resource type="Resource" path="res://economy/buyers/main_fishing_shop.tres" id="12_main_shop"]
[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="PackedScene" path="res://ui/pixelation_reset_overlay.tscn" id="15_reset"]
[node name="Main" type="Node3D"] [node name="Main" type="Node3D"]
script = ExtResource("3_main") script = ExtResource("3_main")
@ -51,5 +53,23 @@ stream = ExtResource("13_title_music")
volume_db = -80.0 volume_db = -80.0
bus = &"Music" bus = &"Music"
[node name="GameUI" parent="." instance=ExtResource("5_ui")] [node name="UIPresentation" type="SubViewportContainer" parent="."]
unique_name_in_owner = true
texture_filter = 1
offset_right = 1152.0
offset_bottom = 648.0
mouse_filter = 0
stretch = true
script = ExtResource("14_pixelation")
[node name="UIViewport" type="SubViewport" parent="UIPresentation"]
transparent_bg = true
handle_input_locally = false
size = Vector2i(1152, 648)
render_target_update_mode = 4
[node name="GameUI" parent="UIPresentation/UIViewport" instance=ExtResource("5_ui")]
unique_name_in_owner = true
[node name="PixelationResetOverlay" parent="." instance=ExtResource("15_reset")]
unique_name_in_owner = true unique_name_in_owner = true

View file

@ -18,7 +18,7 @@ config/icon="res://icon.svg"
[display] [display]
window/stretch/mode="canvas_items" window/stretch/mode="disabled"
window/stretch/aspect="expand" window/stretch/aspect="expand"
[gui] [gui]

View file

@ -7,12 +7,22 @@ const MIN_MOUSE_SENSITIVITY: float = 0.001
const MAX_MOUSE_SENSITIVITY: float = 0.012 const MAX_MOUSE_SENSITIVITY: float = 0.012
const MIN_CONTROLLER_SENSITIVITY: float = 0.5 const MIN_CONTROLLER_SENSITIVITY: float = 0.5
const MAX_CONTROLLER_SENSITIVITY: float = 5.0 const MAX_CONTROLLER_SENSITIVITY: float = 5.0
const MIN_WORLD_PIXEL_SIZE: int = 1
const MAX_WORLD_PIXEL_SIZE: int = 5
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]
@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
@export_range(0.001, 0.012, 0.0005) var mouse_camera_sensitivity: float = 0.005 @export_range(0.001, 0.012, 0.0005) var mouse_camera_sensitivity: float = 0.005
@export_range(0.5, 5.0, 0.1) var controller_camera_sensitivity: float = 2.5 @export_range(0.5, 5.0, 0.1) var controller_camera_sensitivity: float = 2.5
@export var invert_camera_y: bool = false @export var invert_camera_y: bool = false
@export_range(1, 5, 1) var world_pixel_size: int = DEFAULT_WORLD_PIXEL_SIZE
@export_range(1, 5, 1) var ui_pixel_size: int = DEFAULT_UI_PIXEL_SIZE
func is_valid() -> bool: func is_valid() -> bool:
@ -26,6 +36,10 @@ func is_valid() -> bool:
and is_finite(controller_camera_sensitivity) and is_finite(controller_camera_sensitivity)
and controller_camera_sensitivity >= MIN_CONTROLLER_SENSITIVITY and controller_camera_sensitivity >= MIN_CONTROLLER_SENSITIVITY
and controller_camera_sensitivity <= MAX_CONTROLLER_SENSITIVITY and controller_camera_sensitivity <= MAX_CONTROLLER_SENSITIVITY
and world_pixel_size >= MIN_WORLD_PIXEL_SIZE
and world_pixel_size <= MAX_WORLD_PIXEL_SIZE
and ui_pixel_size >= MIN_UI_PIXEL_SIZE
and ui_pixel_size <= MAX_UI_PIXEL_SIZE
) )
@ -36,4 +50,24 @@ func copy() -> PlayerSettings:
result.mouse_camera_sensitivity = mouse_camera_sensitivity result.mouse_camera_sensitivity = mouse_camera_sensitivity
result.controller_camera_sensitivity = controller_camera_sensitivity result.controller_camera_sensitivity = controller_camera_sensitivity
result.invert_camera_y = invert_camera_y result.invert_camera_y = invert_camera_y
result.world_pixel_size = world_pixel_size
result.ui_pixel_size = ui_pixel_size
return result return result
static func get_world_render_scale(pixel_size: int) -> float:
var index: int = clampi(
pixel_size,
MIN_WORLD_PIXEL_SIZE,
MAX_WORLD_PIXEL_SIZE
) - 1
return WORLD_RENDER_SCALES[index]
static func get_ui_render_scale(pixel_size: int) -> float:
var index: int = clampi(
pixel_size,
MIN_UI_PIXEL_SIZE,
MAX_UI_PIXEL_SIZE
) - 1
return UI_RENDER_SCALES[index]

View file

@ -39,6 +39,9 @@ func load_settings() -> bool:
return _use_defaults_after_corruption("Player settings structure is invalid.") return _use_defaults_after_corruption("Player settings structure is invalid.")
var accessibility: Dictionary = data["accessibility"] var accessibility: Dictionary = data["accessibility"]
var camera: Dictionary = data["camera"] var camera: Dictionary = data["camera"]
var presentation: Dictionary = {}
if typeof(data.get("presentation")) == TYPE_DICTIONARY:
presentation = data["presentation"]
if ( if (
typeof(accessibility.get("auto_click_enabled")) != TYPE_BOOL typeof(accessibility.get("auto_click_enabled")) != TYPE_BOOL
or typeof(camera.get("invert_vertical")) != TYPE_BOOL or typeof(camera.get("invert_vertical")) != TYPE_BOOL
@ -59,6 +62,24 @@ func load_settings() -> bool:
-1.0 -1.0
) )
loaded.invert_camera_y = camera["invert_vertical"] loaded.invert_camera_y = camera["invert_vertical"]
loaded.world_pixel_size = _read_clamped_integer(
presentation.get(
"world_pixel_size",
PlayerSettings.DEFAULT_WORLD_PIXEL_SIZE
),
PlayerSettings.MIN_WORLD_PIXEL_SIZE,
PlayerSettings.MAX_WORLD_PIXEL_SIZE,
PlayerSettings.DEFAULT_WORLD_PIXEL_SIZE
)
loaded.ui_pixel_size = _read_clamped_integer(
presentation.get(
"ui_pixel_size",
PlayerSettings.DEFAULT_UI_PIXEL_SIZE
),
PlayerSettings.MIN_UI_PIXEL_SIZE,
PlayerSettings.MAX_UI_PIXEL_SIZE,
PlayerSettings.DEFAULT_UI_PIXEL_SIZE
)
if not loaded.is_valid(): if not loaded.is_valid():
return _use_defaults_after_corruption("Player settings values are invalid.") return _use_defaults_after_corruption("Player settings values are invalid.")
current_settings = loaded current_settings = loaded
@ -80,6 +101,21 @@ func apply_settings(settings: PlayerSettings) -> bool:
return true return true
func apply_pixelation(world_pixel_size: int, ui_pixel_size: int) -> bool:
var edited: PlayerSettings = current_settings.copy()
edited.world_pixel_size = clampi(
world_pixel_size,
PlayerSettings.MIN_WORLD_PIXEL_SIZE,
PlayerSettings.MAX_WORLD_PIXEL_SIZE
)
edited.ui_pixel_size = clampi(
ui_pixel_size,
PlayerSettings.MIN_UI_PIXEL_SIZE,
PlayerSettings.MAX_UI_PIXEL_SIZE
)
return apply_settings(edited)
func save_now() -> bool: func save_now() -> bool:
if current_settings == null or not current_settings.is_valid(): if current_settings == null or not current_settings.is_valid():
return false return false
@ -94,6 +130,10 @@ func save_now() -> bool:
"controller_sensitivity": current_settings.controller_camera_sensitivity, "controller_sensitivity": current_settings.controller_camera_sensitivity,
"invert_vertical": current_settings.invert_camera_y, "invert_vertical": current_settings.invert_camera_y,
}, },
"presentation": {
"world_pixel_size": current_settings.world_pixel_size,
"ui_pixel_size": current_settings.ui_pixel_size,
},
} }
var file := FileAccess.open(TEMP_PATH, FileAccess.WRITE) var file := FileAccess.open(TEMP_PATH, FileAccess.WRITE)
if file == null: if file == null:
@ -160,6 +200,16 @@ func _read_float(value: Variant, fallback: float) -> float:
return result if is_finite(result) else fallback return result if is_finite(result) else fallback
func _read_clamped_integer(
value: Variant,
minimum: int,
maximum: int,
fallback: int,
) -> int:
var result: int = _read_integer(value, fallback)
return clampi(result, minimum, maximum)
func _rename_file(from_path: String, to_path: String) -> bool: func _rename_file(from_path: String, to_path: String) -> bool:
return DirAccess.rename_absolute( return DirAccess.rename_absolute(
ProjectSettings.globalize_path(from_path), ProjectSettings.globalize_path(from_path),

View file

@ -0,0 +1,26 @@
# Bubble menu authoring
Instance `bubble_button.tscn` for each action, or attach `bubble_button.gd`
to an existing Button. Set its neutral size, desktop and compact anchors,
font-size limits, and deterministic motion values in the Inspector. A label
child can be assigned through `label_control_path` when wrapped text is needed.
Text size is calculated once per layout from the neutral smaller dimension and
the profile ratio.
Place the buttons under a Control using `bubble_cluster.gd`. Give the cluster
the ordered BubbleButton references with `configure()`, then call
`apply_layout()` when its available size or responsive layout changes. The
button order defines explicit keyboard and controller focus neighbors. Compact
anchors and minimum sizes remain authored per button.
The shared profile owns the palette, rounded styles, proportional-font ratio,
hover response, and contact tuning. Labels, actions, sizes, anchors, per-button
motion, responsive wrapping, availability, and confirmation behavior remain
owned by the menu. Connect each Button's `pressed` signal in that parent menu.
`motion_scale` defaults to `1.0`. Setting it to `0.0` removes idle drift and
deformation while retaining hover and focus feedback.
Contact is a deterministic, bounded visual correction around authored anchors.
Real physics is intentionally avoided so layouts, focus order, and hit targets
remain stable.

View file

@ -0,0 +1,186 @@
class_name BubbleButton
extends Button
@export var profile: BubbleMenuProfile
@export_group("Authored Layout")
@export var neutral_size: Vector2 = Vector2(120.0, 116.0)
@export var desktop_anchor: Vector2 = Vector2.ZERO
@export var compact_anchor: Vector2 = Vector2.ZERO
@export var compact_minimum_size: Vector2 = Vector2.ZERO
@export_group("Typography")
@export var label_control_path: NodePath
@export_range(1, 256, 1) var minimum_font_size: int = 14
@export_range(1, 256, 1) var maximum_font_size: int = 32
@export_group("Motion")
@export_range(0.0, 32.0, 0.1) var horizontal_amplitude: float = 1.8
@export_range(0.0, 32.0, 0.1) var vertical_amplitude: float = 4.0
@export_range(0.1, 30.0, 0.1) var motion_period: float = 5.0
@export_range(0.0, TAU, 0.01) var motion_phase: float = 0.0
@export_range(0.0, 0.2, 0.001) var deformation_amplitude: float = 0.016
@export_range(0.1, 30.0, 0.1) var deformation_period: float = 6.0
var neutral_position: Vector2 = Vector2.ZERO
var presented_size: Vector2 = Vector2.ZERO
var emphasis: float = 0.0
var _hovered: bool = false
var _focused: bool = false
func _ready() -> void:
mouse_entered.connect(_set_hovered.bind(true))
mouse_exited.connect(_set_hovered.bind(false))
focus_entered.connect(_set_focused.bind(true))
focus_exited.connect(_set_focused.bind(false))
resized.connect(_update_pivot)
_update_pivot()
apply_profile()
func apply_layout(
center: Vector2,
bubble_size: Vector2,
font_size_ratio: float,
) -> void:
position = center - bubble_size * 0.5
size = bubble_size
neutral_position = position
presented_size = bubble_size
_update_pivot()
var label_control: Control = get_label_control()
var font_size := clampi(
roundi(minf(bubble_size.x, bubble_size.y) * font_size_ratio),
minimum_font_size,
maximum_font_size
)
label_control.add_theme_font_size_override("font_size", font_size)
func get_layout_size(layout_scale: float, compact: bool) -> Vector2:
var bubble_size: Vector2 = neutral_size * layout_scale
if compact:
bubble_size.x = maxf(bubble_size.x, compact_minimum_size.x)
bubble_size.y = maxf(bubble_size.y, compact_minimum_size.y)
return bubble_size
func get_authored_anchor(compact: bool) -> Vector2:
return compact_anchor if compact else desktop_anchor
func get_label_control() -> Control:
if not label_control_path.is_empty():
var custom_label := get_node_or_null(label_control_path) as Control
if custom_label != null:
return custom_label
return self
func advance_emphasis(delta: float) -> void:
var target: float = 1.0 if _hovered or _focused else 0.0
emphasis = move_toward(
emphasis,
target,
profile.emphasis_speed * delta
)
func calculate_target(
elapsed: float,
layout_scale: float,
motion_scale: float,
) -> Vector2:
var phase: float = (
elapsed / motion_period * TAU
+ motion_phase
)
var idle_offset := Vector2(
sin(phase * 0.73 + motion_phase) * horizontal_amplitude,
sin(phase) * vertical_amplitude
) * layout_scale * motion_scale
idle_offset.y -= (
profile.hover_focus_lift
* emphasis
* layout_scale
)
return neutral_position + idle_offset
func calculate_visual_scale(
elapsed: float,
motion_scale: float,
) -> Vector2:
var deformation_phase: float = (
elapsed / deformation_period * TAU
+ motion_phase * 1.37
)
var deformation_amount: float = (
sin(deformation_phase)
* deformation_amplitude
* motion_scale
* lerpf(1.0, profile.emphasized_deformation_scale, emphasis)
)
var hover_scale: float = lerpf(
1.0,
profile.hover_focus_scale,
emphasis
)
return Vector2(
1.0 + deformation_amount,
1.0 - deformation_amount
) * hover_scale
func get_visual_radius(visual_scale: Vector2) -> float:
var visual_size: Vector2 = presented_size * visual_scale
return (visual_size.x + visual_size.y) * 0.25
func apply_presentation(
target_position: Vector2,
visual_scale: Vector2,
position_weight: float,
) -> void:
position = position.lerp(target_position, position_weight)
scale = visual_scale
func _has_point(point: Vector2) -> bool:
var radius: Vector2 = size * 0.5
if radius.x <= 0.0 or radius.y <= 0.0:
return false
var normalized: Vector2 = (point - radius) / radius
return normalized.length_squared() <= 1.0
func _set_hovered(value: bool) -> void:
_hovered = value
func _set_focused(value: bool) -> void:
_focused = value
func _update_pivot() -> void:
pivot_offset = size * 0.5
func apply_profile() -> void:
if profile == null:
return
add_theme_stylebox_override("normal", profile.make_normal_style())
var hover_style: StyleBoxFlat = profile.make_hover_style()
add_theme_stylebox_override("hover", hover_style)
add_theme_stylebox_override("focus", hover_style)
add_theme_stylebox_override("pressed", profile.make_pressed_style())
add_theme_stylebox_override("disabled", profile.make_disabled_style())
add_theme_color_override("font_color", profile.text_color)
add_theme_color_override("font_hover_color", profile.text_hover_color)
add_theme_color_override("font_focus_color", profile.text_hover_color)
add_theme_color_override("font_pressed_color", profile.text_pressed_color)
add_theme_color_override("font_disabled_color", profile.text_disabled_color)
var label_control: Control = get_label_control()
if label_control != self:
label_control.add_theme_color_override("font_color", profile.text_color)

View file

@ -0,0 +1 @@
uid://d15kke1iwoueg

View file

@ -0,0 +1,9 @@
[gd_scene load_steps=3 format=3]
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_button.gd" id="1_button"]
[ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="2_profile"]
[node name="BubbleButton" type="Button"]
custom_minimum_size = Vector2(120, 116)
script = ExtResource("1_button")
profile = ExtResource("2_profile")

View file

@ -0,0 +1,117 @@
class_name BubbleCluster
extends Control
@export var profile: BubbleMenuProfile
@export var desktop_reference_size: Vector2 = Vector2(396.0, 318.0)
@export var compact_reference_size: Vector2 = Vector2(294.0, 200.0)
@export_range(0.0, 1.0, 0.01) var motion_scale: float = 1.0
var _bubbles: Array[BubbleButton] = []
var _elapsed: float = 0.0
var _layout_scale: float = 1.0
func configure(bubbles: Array[BubbleButton]) -> void:
_bubbles = bubbles
for index: int in _bubbles.size():
var bubble: BubbleButton = _bubbles[index]
if bubble.profile == null:
bubble.profile = profile
bubble.apply_profile()
if index > 0:
bubble.focus_neighbor_top = bubble.get_path_to(
_bubbles[index - 1]
)
if index + 1 < _bubbles.size():
bubble.focus_neighbor_bottom = bubble.get_path_to(
_bubbles[index + 1]
)
func apply_layout(field_size: Vector2, compact: bool) -> void:
_layout_scale = minf(
field_size.x / desktop_reference_size.x,
field_size.y / desktop_reference_size.y
)
var authored_extent: Vector2 = desktop_reference_size * _layout_scale
var layout_origin := Vector2(
(field_size.x - authored_extent.x) * 0.5,
(field_size.y - authored_extent.y) * 0.5
)
for bubble: BubbleButton in _bubbles:
var bubble_size: Vector2 = bubble.get_layout_size(
_layout_scale,
compact
)
var center: Vector2 = (
layout_origin
+ bubble.get_authored_anchor(compact) * _layout_scale
)
bubble.apply_layout(
center,
bubble_size,
profile.font_size_ratio
)
func advance_motion(delta: float) -> void:
_elapsed = fmod(_elapsed + delta, 120.0)
var targets: Array[Vector2] = []
var visual_radii: Array[float] = []
var visual_scales: Array[Vector2] = []
for bubble: BubbleButton in _bubbles:
bubble.advance_emphasis(delta)
var target: Vector2 = bubble.calculate_target(
_elapsed,
_layout_scale,
motion_scale
)
var visual_scale: Vector2 = bubble.calculate_visual_scale(
_elapsed,
motion_scale
)
targets.append(target)
visual_scales.append(visual_scale)
visual_radii.append(bubble.get_visual_radius(visual_scale))
for first_index: int in _bubbles.size():
for second_index: int in range(first_index + 1, _bubbles.size()):
var first_center: Vector2 = (
targets[first_index]
+ _bubbles[first_index].presented_size * 0.5
)
var second_center: Vector2 = (
targets[second_index]
+ _bubbles[second_index].presented_size * 0.5
)
var center_delta: Vector2 = second_center - first_center
var distance: float = center_delta.length()
var desired_distance: float = (
visual_radii[first_index]
+ visual_radii[second_index]
+ profile.contact_gap * _layout_scale
)
if distance >= desired_distance:
continue
var direction := (
center_delta / distance
if distance > 0.001
else Vector2.RIGHT.rotated(float(first_index + 1))
)
var correction: Vector2 = (
direction
* minf(
(desired_distance - distance) * 0.5,
profile.maximum_separation * _layout_scale
)
)
targets[first_index] -= correction
targets[second_index] += correction
var position_weight: float = 1.0 - exp(
-profile.position_response * delta
)
for index: int in _bubbles.size():
_bubbles[index].apply_presentation(
targets[index],
visual_scales[index],
position_weight
)

View file

@ -0,0 +1 @@
uid://cvqif55pw6r32

View file

@ -0,0 +1,91 @@
class_name BubbleMenuProfile
extends Resource
@export_group("Typography")
@export_range(0.01, 1.0, 0.001) var font_size_ratio: float = 0.17
@export_group("Interaction")
@export_range(1.0, 1.2, 0.001) var hover_focus_scale: float = 1.035
@export_range(0.0, 16.0, 0.1) var hover_focus_lift: float = 2.0
@export_range(0.1, 30.0, 0.1) var emphasis_speed: float = 10.0
@export_range(0.0, 1.0, 0.01) var emphasized_deformation_scale: float = 0.6
@export_group("Cluster")
@export_range(0.1, 30.0, 0.1) var position_response: float = 8.0
@export_range(0.0, 24.0, 0.1) var contact_gap: float = 3.0
@export_range(0.0, 24.0, 0.1) var maximum_separation: float = 3.0
@export_group("Shape")
@export_range(0.0, 32.0, 0.5) var content_margin: float = 8.0
@export_range(0, 512, 1) var corner_radius: int = 128
@export_range(0, 12, 1) var normal_border_width: int = 2
@export_range(0, 12, 1) var emphasized_border_width: int = 3
@export_group("Colors")
@export var normal_fill: Color = Color(0.82, 0.93, 0.96, 0.96)
@export var normal_border: Color = Color(0.91, 0.98, 1.0, 0.85)
@export var hover_fill: Color = Color(0.91, 0.975, 0.985, 1.0)
@export var hover_border: Color = Color(1.0, 1.0, 1.0, 0.95)
@export var pressed_fill: Color = Color(0.7, 0.87, 0.92, 1.0)
@export var pressed_border: Color = Color(0.94, 0.99, 1.0, 1.0)
@export var disabled_fill: Color = Color(0.66, 0.77, 0.8, 0.78)
@export var disabled_border: Color = Color(0.81, 0.9, 0.92, 0.55)
@export var text_color: Color = Color(0.035, 0.145, 0.22, 1.0)
@export var text_hover_color: Color = Color(0.025, 0.12, 0.19, 1.0)
@export var text_pressed_color: Color = Color(0.025, 0.11, 0.17, 1.0)
@export var text_disabled_color: Color = Color(0.16, 0.25, 0.29, 0.72)
func make_normal_style() -> StyleBoxFlat:
return _make_style(
normal_fill,
normal_border,
normal_border_width
)
func make_hover_style() -> StyleBoxFlat:
return _make_style(
hover_fill,
hover_border,
emphasized_border_width
)
func make_pressed_style() -> StyleBoxFlat:
return _make_style(
pressed_fill,
pressed_border,
emphasized_border_width
)
func make_disabled_style() -> StyleBoxFlat:
return _make_style(
disabled_fill,
disabled_border,
normal_border_width
)
func _make_style(
fill_color: Color,
outline_color: Color,
outline_width: int,
) -> StyleBoxFlat:
var style := StyleBoxFlat.new()
style.content_margin_left = content_margin
style.content_margin_top = content_margin
style.content_margin_right = content_margin
style.content_margin_bottom = content_margin
style.bg_color = fill_color
style.border_width_left = outline_width
style.border_width_top = outline_width
style.border_width_right = outline_width
style.border_width_bottom = outline_width
style.border_color = outline_color
style.corner_radius_top_left = corner_radius
style.corner_radius_top_right = corner_radius
style.corner_radius_bottom_right = corner_radius
style.corner_radius_bottom_left = corner_radius
return style

View file

@ -0,0 +1 @@
uid://ccliyvoivfimr

View file

@ -0,0 +1,6 @@
[gd_resource type="Resource" script_class="BubbleMenuProfile" load_steps=2 format=3]
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_menu_profile.gd" id="1_profile"]
[resource]
script = ExtResource("1_profile")

View file

@ -38,10 +38,10 @@ anchor_left = 0.5
anchor_top = 0.5 anchor_top = 0.5
anchor_right = 0.5 anchor_right = 0.5
anchor_bottom = 0.5 anchor_bottom = 0.5
offset_left = -480.0 offset_left = -560.0
offset_top = -310.0 offset_top = -320.0
offset_right = 480.0 offset_right = 560.0
offset_bottom = 310.0 offset_bottom = 320.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
@ -64,7 +64,7 @@ theme_override_constants/separation = 8
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
text = "fishing shop" text = "fishing shop"
theme_override_font_sizes/font_size = 24 theme_override_font_sizes/font_size = 36
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1) theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
[node name="CoinIcon" type="TextureRect" parent="ShopPanel/Margin/Layout/Header"] [node name="CoinIcon" type="TextureRect" parent="ShopPanel/Margin/Layout/Header"]
@ -84,7 +84,7 @@ text = "wallet: $0"
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "close" text = "close"
custom_minimum_size = Vector2(72, 30) custom_minimum_size = Vector2(110, 58)
[node name="Feedback" type="Label" parent="ShopPanel/Margin/Layout"] [node name="Feedback" type="Label" parent="ShopPanel/Margin/Layout"]
unique_name_in_owner = true unique_name_in_owner = true
@ -108,7 +108,7 @@ theme_override_constants/separation = 6
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "sell individual cooler fish • full base value" text = "sell individual cooler fish • full base value"
theme_override_font_sizes/font_size = 17 theme_override_font_sizes/font_size = 26
[node name="SalesBody" type="HSplitContainer" parent="ShopPanel/Margin/Layout/Body/FishSales"] [node name="SalesBody" type="HSplitContainer" parent="ShopPanel/Margin/Layout/Body/FishSales"]
layout_mode = 2 layout_mode = 2
@ -173,7 +173,7 @@ unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "select a fish" text = "select a fish"
horizontal_alignment = 1 horizontal_alignment = 1
theme_override_font_sizes/font_size = 19 theme_override_font_sizes/font_size = 29
[node name="FishDetails" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin/DetailStack"] [node name="FishDetails" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin/DetailStack"]
unique_name_in_owner = true unique_name_in_owner = true
@ -204,7 +204,7 @@ theme_override_constants/separation = 8
[node name="UpgradeTitle" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades"] [node name="UpgradeTitle" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades"]
layout_mode = 2 layout_mode = 2
text = "fishing upgrades" text = "fishing upgrades"
theme_override_font_sizes/font_size = 17 theme_override_font_sizes/font_size = 26
[node name="Supplies" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body"] [node name="Supplies" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body"]
custom_minimum_size = Vector2(210, 0) custom_minimum_size = Vector2(210, 0)
@ -214,7 +214,7 @@ theme_override_constants/separation = 6
[node name="Title" type="Label" parent="ShopPanel/Margin/Layout/Body/Supplies"] [node name="Title" type="Label" parent="ShopPanel/Margin/Layout/Body/Supplies"]
layout_mode = 2 layout_mode = 2
text = "supplies" text = "supplies"
theme_override_font_sizes/font_size = 17 theme_override_font_sizes/font_size = 26
[node name="Scroll" type="ScrollContainer" parent="ShopPanel/Margin/Layout/Body/Supplies"] [node name="Scroll" type="ScrollContainer" parent="ShopPanel/Margin/Layout/Body/Supplies"]
layout_mode = 2 layout_mode = 2
@ -256,7 +256,7 @@ size_flags_horizontal = 3
[node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"] [node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
layout_mode = 2 layout_mode = 2
text = "reel speed" text = "reel speed"
theme_override_font_sizes/font_size = 17 theme_override_font_sizes/font_size = 26
[node name="ReelLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"] [node name="ReelLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
unique_name_in_owner = true unique_name_in_owner = true
@ -307,7 +307,7 @@ size_flags_horizontal = 3
[node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"] [node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"]
layout_mode = 2 layout_mode = 2
text = "barrier power" text = "barrier power"
theme_override_font_sizes/font_size = 17 theme_override_font_sizes/font_size = 26
[node name="BarrierLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"] [node name="BarrierLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"]
unique_name_in_owner = true unique_name_in_owner = true
@ -358,7 +358,7 @@ size_flags_horizontal = 3
[node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"] [node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"]
layout_mode = 2 layout_mode = 2
text = "cooler capacity" text = "cooler capacity"
theme_override_font_sizes/font_size = 17 theme_override_font_sizes/font_size = 26
[node name="CoolerLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"] [node name="CoolerLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"]
unique_name_in_owner = true unique_name_in_owner = true
@ -390,10 +390,10 @@ anchor_left = 0.5
anchor_top = 0.5 anchor_top = 0.5
anchor_right = 0.5 anchor_right = 0.5
anchor_bottom = 0.5 anchor_bottom = 0.5
offset_left = -220.0 offset_left = -320.0
offset_top = -90.0 offset_top = -150.0
offset_right = 220.0 offset_right = 320.0
offset_bottom = 90.0 offset_bottom = 150.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2

View file

@ -146,7 +146,7 @@ corner_radius_bottom_left = 6
[resource] [resource]
default_font = ExtResource("1_font") default_font = ExtResource("1_font")
default_font_size = 15 default_font_size = 23
Label/colors/font_color = Color(0.956, 0.972, 0.976, 1) Label/colors/font_color = Color(0.956, 0.972, 0.976, 1)
Label/colors/font_shadow_color = Color(0, 0, 0, 0) Label/colors/font_shadow_color = Color(0, 0, 0, 0)
PanelContainer/styles/panel = SubResource("Panel") PanelContainer/styles/panel = SubResource("Panel")

View file

@ -17,6 +17,7 @@ const HotbarUIType = preload("res://ui/hotbar.gd")
const TitleScreenType = preload("res://ui/title_screen.gd") const TitleScreenType = preload("res://ui/title_screen.gd")
const PauseMenuType = preload("res://ui/pause_menu.gd") const PauseMenuType = preload("res://ui/pause_menu.gd")
const FishingShopType = preload("res://ui/fishing_shop.gd") const FishingShopType = preload("res://ui/fishing_shop.gd")
const SettingsPanelType = preload("res://ui/settings_panel.gd")
const PlayerFishingUpgradesType = preload( const PlayerFishingUpgradesType = preload(
"res://progression/player_fishing_upgrades.gd" "res://progression/player_fishing_upgrades.gd"
) )
@ -30,6 +31,10 @@ const PlayerCoolerCapacityType = preload(
"res://progression/player_cooler_capacity.gd" "res://progression/player_cooler_capacity.gd"
) )
signal pixelation_settings_visibility_changed(is_visible: bool)
signal crisp_reset_focus_requested
signal interactive_pointer_ui_changed(is_open: bool)
@onready var _status_label: Label = %StatusLabel @onready var _status_label: Label = %StatusLabel
@onready var _catch_track: Control = %CatchTrack @onready var _catch_track: Control = %CatchTrack
@onready var _green_catch_progress: ProgressBar = %GreenCatchProgress @onready var _green_catch_progress: ProgressBar = %GreenCatchProgress
@ -47,6 +52,12 @@ const PlayerCoolerCapacityType = preload(
@onready var _fishing_shop: FishingShopType = %FishingShop @onready var _fishing_shop: FishingShopType = %FishingShop
@onready var _shop_prompt: PanelContainer = %ShopPrompt @onready var _shop_prompt: PanelContainer = %ShopPrompt
@onready var _effect_status: Label = %EffectStatus @onready var _effect_status: Label = %EffectStatus
@onready var _title_settings_panel: SettingsPanelType = (
$UIRoot/TitleScreen/SettingsPanel
)
@onready var _pause_settings_panel: SettingsPanelType = (
$UIRoot/PauseMenu/SettingsCenter/SettingsPanel
)
var _showcase_active: bool = false var _showcase_active: bool = false
var _player_menu_open: bool = false var _player_menu_open: bool = false
@ -57,6 +68,21 @@ var _shop_open: bool = false
var _item_effects: PlayerItemEffectsType var _item_effects: PlayerItemEffectsType
func _ready() -> void:
_title_settings_panel.panel_visibility_changed.connect(
_on_settings_visibility_changed
)
_pause_settings_panel.panel_visibility_changed.connect(
_on_settings_visibility_changed
)
_title_settings_panel.crisp_reset_focus_requested.connect(
crisp_reset_focus_requested.emit
)
_pause_settings_panel.crisp_reset_focus_requested.connect(
crisp_reset_focus_requested.emit
)
func setup( func setup(
player: PlayerType, player: PlayerType,
inventory: FishInventoryType, inventory: FishInventoryType,
@ -195,6 +221,7 @@ func set_system_menu_open(is_open: bool) -> void:
_hotbar_ui.set_drag_enabled(_player_menu_open and not is_open) _hotbar_ui.set_drag_enabled(_player_menu_open and not is_open)
if is_open: if is_open:
_shop_prompt.hide() _shop_prompt.hide()
_emit_interactive_pointer_ui_changed()
func get_fishing_shop() -> FishingShopType: func get_fishing_shop() -> FishingShopType:
@ -219,6 +246,29 @@ func get_title_screen() -> TitleScreenType:
return _title_screen return _title_screen
func set_effective_ui_pixel_size(pixel_size: int) -> void:
_title_settings_panel.set_effective_ui_pixel_size(pixel_size)
_pause_settings_panel.set_effective_ui_pixel_size(pixel_size)
func focus_open_settings_back_button() -> void:
if _title_settings_panel.visible:
_title_settings_panel.focus_back_button()
elif _pause_settings_panel.visible:
_pause_settings_panel.focus_back_button()
func refresh_open_settings_panel() -> void:
_title_settings_panel.refresh_open_panel()
_pause_settings_panel.refresh_open_panel()
func _on_settings_visibility_changed(_is_visible: bool) -> void:
pixelation_settings_visibility_changed.emit(
_title_settings_panel.visible or _pause_settings_panel.visible
)
func _on_fishing_status_changed(status: String) -> void: func _on_fishing_status_changed(status: String) -> void:
if _showcase_active: if _showcase_active:
return return
@ -391,6 +441,7 @@ func _on_player_menu_visibility_changed(is_open: bool) -> void:
) )
_hotbar_ui.set_drag_enabled(is_open) _hotbar_ui.set_drag_enabled(is_open)
_refresh_fishing_panel_visibility() _refresh_fishing_panel_visibility()
_emit_interactive_pointer_ui_changed()
if is_open: if is_open:
_shop_prompt.hide() _shop_prompt.hide()
@ -408,3 +459,10 @@ func _on_shop_visibility_changed(is_open: bool) -> void:
) )
if is_open: if is_open:
_shop_prompt.hide() _shop_prompt.hide()
_emit_interactive_pointer_ui_changed()
func _emit_interactive_pointer_ui_changed() -> void:
interactive_pointer_ui_changed.emit(
_system_menu_open or _player_menu_open or _shop_open
)

View file

@ -36,7 +36,11 @@ bg_color = Color(0, 0, 0, 0)
[node name="GameUI" type="CanvasLayer"] [node name="GameUI" type="CanvasLayer"]
script = ExtResource("1_ui") script = ExtResource("1_ui")
[node name="FishingPanel" type="PanelContainer" parent="."] [node name="UIRoot" type="Control" parent="."]
unique_name_in_owner = true
mouse_filter = 2
[node name="FishingPanel" type="PanelContainer" parent="UIRoot"]
unique_name_in_owner = true unique_name_in_owner = true
visible = false visible = false
z_index = 60 z_index = 60
@ -54,33 +58,33 @@ grow_vertical = 0
mouse_filter = 2 mouse_filter = 2
theme = ExtResource("3_theme") theme = ExtResource("3_theme")
[node name="MarginContainer" type="MarginContainer" parent="FishingPanel"] [node name="MarginContainer" type="MarginContainer" parent="UIRoot/FishingPanel"]
theme_override_constants/margin_left = 14 theme_override_constants/margin_left = 14
theme_override_constants/margin_top = 8 theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 14 theme_override_constants/margin_right = 14
theme_override_constants/margin_bottom = 8 theme_override_constants/margin_bottom = 8
[node name="VBoxContainer" type="VBoxContainer" parent="FishingPanel/MarginContainer"] [node name="VBoxContainer" type="VBoxContainer" parent="UIRoot/FishingPanel/MarginContainer"]
theme_override_constants/separation = 4 theme_override_constants/separation = 4
[node name="StatusLabel" type="Label" parent="FishingPanel/MarginContainer/VBoxContainer"] [node name="StatusLabel" type="Label" parent="UIRoot/FishingPanel/MarginContainer/VBoxContainer"]
unique_name_in_owner = true unique_name_in_owner = true
visible = false visible = false
text = "" text = ""
horizontal_alignment = 1 horizontal_alignment = 1
theme_override_font_sizes/font_size = 16 theme_override_font_sizes/font_size = 16
[node name="ShowcaseDetails" type="Label" parent="FishingPanel/MarginContainer/VBoxContainer"] [node name="ShowcaseDetails" type="Label" parent="UIRoot/FishingPanel/MarginContainer/VBoxContainer"]
unique_name_in_owner = true unique_name_in_owner = true
visible = false visible = false
horizontal_alignment = 1 horizontal_alignment = 1
[node name="CatchTrack" type="Control" parent="FishingPanel/MarginContainer/VBoxContainer"] [node name="CatchTrack" type="Control" parent="UIRoot/FishingPanel/MarginContainer/VBoxContainer"]
unique_name_in_owner = true unique_name_in_owner = true
visible = false visible = false
custom_minimum_size = Vector2(0, 20) custom_minimum_size = Vector2(0, 20)
[node name="GreenCatchProgress" type="ProgressBar" parent="FishingPanel/MarginContainer/VBoxContainer/CatchTrack"] [node name="GreenCatchProgress" type="ProgressBar" parent="UIRoot/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
@ -94,7 +98,7 @@ theme_override_styles/fill = SubResource("StyleBox_catch_fill")
value = 0.0 value = 0.0
show_percentage = false show_percentage = false
[node name="RedChaseProgress" type="ProgressBar" parent="FishingPanel/MarginContainer/VBoxContainer/CatchTrack"] [node name="RedChaseProgress" type="ProgressBar" parent="UIRoot/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
@ -108,7 +112,7 @@ theme_override_styles/fill = SubResource("StyleBox_chase_fill")
value = 0.0 value = 0.0
show_percentage = false show_percentage = false
[node name="BarrierMarkers" type="Control" parent="FishingPanel/MarginContainer/VBoxContainer/CatchTrack"] [node name="BarrierMarkers" type="Control" parent="UIRoot/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
@ -118,23 +122,23 @@ grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
mouse_filter = 2 mouse_filter = 2
[node name="BarrierSummary" type="Label" parent="FishingPanel/MarginContainer/VBoxContainer"] [node name="BarrierSummary" type="Label" parent="UIRoot/FishingPanel/MarginContainer/VBoxContainer"]
unique_name_in_owner = true unique_name_in_owner = true
visible = false visible = false
horizontal_alignment = 1 horizontal_alignment = 1
[node name="BarrierHealth" type="Label" parent="FishingPanel/MarginContainer/VBoxContainer"] [node name="BarrierHealth" type="Label" parent="UIRoot/FishingPanel/MarginContainer/VBoxContainer"]
unique_name_in_owner = true unique_name_in_owner = true
visible = false visible = false
horizontal_alignment = 1 horizontal_alignment = 1
[node name="PlayerMenu" parent="." instance=ExtResource("2_menu")] [node name="PlayerMenu" parent="UIRoot" instance=ExtResource("2_menu")]
unique_name_in_owner = true unique_name_in_owner = true
[node name="Hotbar" parent="." instance=ExtResource("7_hotbar")] [node name="Hotbar" parent="UIRoot" instance=ExtResource("7_hotbar")]
unique_name_in_owner = true unique_name_in_owner = true
[node name="ShopPrompt" type="PanelContainer" parent="."] [node name="ShopPrompt" type="PanelContainer" parent="UIRoot"]
unique_name_in_owner = true unique_name_in_owner = true
visible = false visible = false
z_index = 55 z_index = 55
@ -152,22 +156,22 @@ grow_vertical = 0
mouse_filter = 2 mouse_filter = 2
theme = ExtResource("3_theme") theme = ExtResource("3_theme")
[node name="Margin" type="MarginContainer" parent="ShopPrompt"] [node name="Margin" type="MarginContainer" parent="UIRoot/ShopPrompt"]
layout_mode = 2 layout_mode = 2
theme_override_constants/margin_left = 12 theme_override_constants/margin_left = 12
theme_override_constants/margin_top = 7 theme_override_constants/margin_top = 7
theme_override_constants/margin_right = 12 theme_override_constants/margin_right = 12
theme_override_constants/margin_bottom = 7 theme_override_constants/margin_bottom = 7
[node name="Label" type="Label" parent="ShopPrompt/Margin"] [node name="Label" type="Label" parent="UIRoot/ShopPrompt/Margin"]
layout_mode = 2 layout_mode = 2
text = "press e to open fishing shop" text = "press e to open fishing shop"
horizontal_alignment = 1 horizontal_alignment = 1
[node name="FishingShop" parent="." instance=ExtResource("8_shop")] [node name="FishingShop" parent="UIRoot" instance=ExtResource("8_shop")]
unique_name_in_owner = true unique_name_in_owner = true
[node name="EffectStatus" type="Label" parent="."] [node name="EffectStatus" type="Label" parent="UIRoot"]
unique_name_in_owner = true unique_name_in_owner = true
visible = false visible = false
z_index = 54 z_index = 54
@ -181,7 +185,7 @@ theme_override_colors/font_shadow_color = Color(0, 0, 0, 0.9)
theme_override_constants/shadow_offset_x = 2 theme_override_constants/shadow_offset_x = 2
theme_override_constants/shadow_offset_y = 2 theme_override_constants/shadow_offset_y = 2
[node name="ScreenFade" type="ColorRect" parent="."] [node name="ScreenFade" type="ColorRect" parent="UIRoot"]
unique_name_in_owner = true unique_name_in_owner = true
visible = false visible = false
z_index = 100 z_index = 100
@ -194,8 +198,8 @@ mouse_filter = 0
color = Color(0, 0, 0, 1) color = Color(0, 0, 0, 1)
script = ExtResource("4_fade") script = ExtResource("4_fade")
[node name="TitleScreen" parent="." instance=ExtResource("5_title")] [node name="TitleScreen" parent="UIRoot" instance=ExtResource("5_title")]
unique_name_in_owner = true unique_name_in_owner = true
[node name="PauseMenu" parent="." instance=ExtResource("6_pause")] [node name="PauseMenu" parent="UIRoot" instance=ExtResource("6_pause")]
unique_name_in_owner = true unique_name_in_owner = true

View file

@ -38,7 +38,7 @@ grow_vertical = 2
[node name="RootPanel" type="PanelContainer" parent="RootCenter"] [node name="RootPanel" type="PanelContainer" parent="RootCenter"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(420, 0) custom_minimum_size = Vector2(560, 0)
layout_mode = 2 layout_mode = 2
[node name="Margin" type="MarginContainer" parent="RootCenter/RootPanel"] [node name="Margin" type="MarginContainer" parent="RootCenter/RootPanel"]
@ -54,50 +54,50 @@ theme_override_constants/separation = 11
[node name="Title" type="Label" parent="RootCenter/RootPanel/Margin/Content"] [node name="Title" type="Label" parent="RootCenter/RootPanel/Margin/Content"]
layout_mode = 2 layout_mode = 2
theme_override_font_sizes/font_size = 30 theme_override_font_sizes/font_size = 45
text = "game menu" text = "game menu"
horizontal_alignment = 1 horizontal_alignment = 1
[node name="ResumeButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"] [node name="ResumeButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 43) custom_minimum_size = Vector2(0, 64)
layout_mode = 2 layout_mode = 2
text = "return to game" text = "return to game"
[node name="SaveButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"] [node name="SaveButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 43) custom_minimum_size = Vector2(0, 64)
layout_mode = 2 layout_mode = 2
text = "save now" text = "save now"
[node name="SettingsButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"] [node name="SettingsButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 43) custom_minimum_size = Vector2(0, 64)
layout_mode = 2 layout_mode = 2
text = "settings" text = "settings"
[node name="ReturnToTitleButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"] [node name="ReturnToTitleButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 43) custom_minimum_size = Vector2(0, 64)
layout_mode = 2 layout_mode = 2
text = "return to title" text = "return to title"
[node name="ResetProgressButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"] [node name="ResetProgressButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 43) custom_minimum_size = Vector2(0, 64)
layout_mode = 2 layout_mode = 2
theme_type_variation = &"DangerButton" theme_type_variation = &"DangerButton"
text = "reset progress" text = "reset progress"
[node name="QuitButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"] [node name="QuitButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 43) custom_minimum_size = Vector2(0, 64)
layout_mode = 2 layout_mode = 2
text = "quit" text = "quit"
[node name="FeedbackLabel" type="Label" parent="RootCenter/RootPanel/Margin/Content"] [node name="FeedbackLabel" type="Label" parent="RootCenter/RootPanel/Margin/Content"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 34) custom_minimum_size = Vector2(0, 54)
layout_mode = 2 layout_mode = 2
horizontal_alignment = 1 horizontal_alignment = 1
vertical_alignment = 1 vertical_alignment = 1
@ -128,7 +128,7 @@ mouse_filter = 2
[node name="ConfirmationPanel" type="PanelContainer" parent="ConfirmationCenter"] [node name="ConfirmationPanel" type="PanelContainer" parent="ConfirmationCenter"]
unique_name_in_owner = true unique_name_in_owner = true
visible = false visible = false
custom_minimum_size = Vector2(520, 0) custom_minimum_size = Vector2(700, 0)
layout_mode = 2 layout_mode = 2
[node name="Margin" type="MarginContainer" parent="ConfirmationCenter/ConfirmationPanel"] [node name="Margin" type="MarginContainer" parent="ConfirmationCenter/ConfirmationPanel"]
@ -146,7 +146,7 @@ theme_override_constants/separation = 15
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
theme_override_colors/font_color = Color(0.937, 0.357, 0.384, 1) theme_override_colors/font_color = Color(0.937, 0.357, 0.384, 1)
theme_override_font_sizes/font_size = 24 theme_override_font_sizes/font_size = 36
text = "confirm" text = "confirm"
horizontal_alignment = 1 horizontal_alignment = 1
@ -164,12 +164,12 @@ alignment = 1
[node name="ConfirmButton" type="Button" parent="ConfirmationCenter/ConfirmationPanel/Margin/Content/Buttons"] [node name="ConfirmButton" type="Button" parent="ConfirmationCenter/ConfirmationPanel/Margin/Content/Buttons"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(190, 43) custom_minimum_size = Vector2(240, 64)
layout_mode = 2 layout_mode = 2
text = "confirm" text = "confirm"
[node name="CancelConfirmButton" type="Button" parent="ConfirmationCenter/ConfirmationPanel/Margin/Content/Buttons"] [node name="CancelConfirmButton" type="Button" parent="ConfirmationCenter/ConfirmationPanel/Margin/Content/Buttons"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(130, 43) custom_minimum_size = Vector2(180, 64)
layout_mode = 2 layout_mode = 2
text = "cancel" text = "cancel"

View file

@ -0,0 +1,32 @@
class_name PixelationResetOverlay
extends CanvasLayer
signal reset_requested
signal return_to_settings_requested
@onready var _reset_button: Button = %ResetPixelationButton
func _ready() -> void:
_reset_button.pressed.connect(reset_requested.emit)
_reset_button.gui_input.connect(_on_reset_button_gui_input)
func set_settings_open(is_open: bool) -> void:
visible = is_open
if not is_open and _reset_button.has_focus():
_reset_button.release_focus()
func focus_reset_button() -> void:
if visible:
_reset_button.grab_focus()
func _on_reset_button_gui_input(event: InputEvent) -> void:
if (
event.is_action_pressed("ui_up")
or event.is_action_pressed("ui_focus_prev")
):
return_to_settings_requested.emit()
get_viewport().set_input_as_handled()

View file

@ -0,0 +1 @@
uid://bg7x4pmgy6mhn

View file

@ -0,0 +1,30 @@
[gd_scene load_steps=3 format=3]
[ext_resource type="Script" path="res://ui/pixelation_reset_overlay.gd" id="1_script"]
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
[node name="PixelationResetOverlay" type="CanvasLayer"]
layer = 300
visible = false
script = ExtResource("1_script")
[node name="ResetAnchor" type="MarginContainer" parent="."]
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -324.0
offset_top = -86.0
offset_right = -16.0
offset_bottom = -16.0
grow_horizontal = 0
grow_vertical = 0
mouse_filter = 2
theme = ExtResource("2_theme")
[node name="ResetPixelationButton" type="Button" parent="ResetAnchor"]
unique_name_in_owner = true
custom_minimum_size = Vector2(308, 70)
layout_mode = 2
text = "reset pixelation"

View file

@ -587,7 +587,7 @@ func _create_inventory_card(fish_catch: FishCatchType) -> Button:
"font_color", "font_color",
UIPalette.SECONDARY UIPalette.SECONDARY
) )
favorite_marker.add_theme_font_size_override("font_size", 18) favorite_marker.add_theme_font_size_override("font_size", 27)
favorite_marker.set_anchors_preset(Control.PRESET_TOP_RIGHT) favorite_marker.set_anchors_preset(Control.PRESET_TOP_RIGHT)
favorite_marker.offset_left = -28.0 favorite_marker.offset_left = -28.0
favorite_marker.offset_top = 5.0 favorite_marker.offset_top = 5.0
@ -603,7 +603,7 @@ func _create_inventory_card(fish_catch: FishCatchType) -> Button:
"font_color", "font_color",
UIPalette.SUCCESS UIPalette.SUCCESS
) )
selected_marker.add_theme_font_size_override("font_size", 18) selected_marker.add_theme_font_size_override("font_size", 27)
selected_marker.set_anchors_preset(Control.PRESET_TOP_LEFT) selected_marker.set_anchors_preset(Control.PRESET_TOP_LEFT)
selected_marker.offset_left = 8.0 selected_marker.offset_left = 8.0
selected_marker.offset_top = 5.0 selected_marker.offset_top = 5.0

View file

@ -29,10 +29,10 @@ color = Color(0.015, 0.02, 0.03, 0.72)
[node name="MenuPanel" type="PanelContainer" parent="."] [node name="MenuPanel" type="PanelContainer" parent="."]
layout_mode = 1 layout_mode = 1
anchor_left = 0.07 anchor_left = 0.02
anchor_top = 0.055 anchor_top = 0.025
anchor_right = 0.93 anchor_right = 0.98
anchor_bottom = 0.72 anchor_bottom = 0.96
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
@ -55,7 +55,7 @@ theme_override_constants/separation = 8
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1) theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
theme_override_font_sizes/font_size = 22 theme_override_font_sizes/font_size = 33
text = "player menu" text = "player menu"
[node name="WalletBalance" type="Label" parent="MenuPanel/Margin/Layout/Header"] [node name="WalletBalance" type="Label" parent="MenuPanel/Margin/Layout/Header"]
@ -69,27 +69,27 @@ unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
toggle_mode = true toggle_mode = true
text = "cooler" text = "cooler"
custom_minimum_size = Vector2(88, 30) custom_minimum_size = Vector2(130, 58)
[node name="BagTab" type="Button" parent="MenuPanel/Margin/Layout/Header"] [node name="BagTab" type="Button" parent="MenuPanel/Margin/Layout/Header"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
toggle_mode = true toggle_mode = true
text = "bag" text = "bag"
custom_minimum_size = Vector2(76, 30) custom_minimum_size = Vector2(110, 58)
[node name="LogbookTab" type="Button" parent="MenuPanel/Margin/Layout/Header"] [node name="LogbookTab" type="Button" parent="MenuPanel/Margin/Layout/Header"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
toggle_mode = true toggle_mode = true
text = "logbook" text = "logbook"
custom_minimum_size = Vector2(88, 30) custom_minimum_size = Vector2(140, 58)
[node name="CloseButton" type="Button" parent="MenuPanel/Margin/Layout/Header"] [node name="CloseButton" type="Button" parent="MenuPanel/Margin/Layout/Header"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "close" text = "close"
custom_minimum_size = Vector2(68, 30) custom_minimum_size = Vector2(110, 58)
[node name="Separator" type="HSeparator" parent="MenuPanel/Margin/Layout"] [node name="Separator" type="HSeparator" parent="MenuPanel/Margin/Layout"]
layout_mode = 2 layout_mode = 2
@ -100,7 +100,7 @@ layout_mode = 2
text = "" text = ""
horizontal_alignment = 1 horizontal_alignment = 1
theme_override_colors/font_color = Color(1, 0.82, 0.4, 1) theme_override_colors/font_color = Color(1, 0.82, 0.4, 1)
theme_override_font_sizes/font_size = 14 theme_override_font_sizes/font_size = 21
[node name="Content" type="Control" parent="MenuPanel/Margin/Layout"] [node name="Content" type="Control" parent="MenuPanel/Margin/Layout"]
custom_minimum_size = Vector2(0, 260) custom_minimum_size = Vector2(0, 260)
@ -128,12 +128,12 @@ text = "sort:"
[node name="SortOption" type="OptionButton" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"] [node name="SortOption" type="OptionButton" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
custom_minimum_size = Vector2(130, 30) custom_minimum_size = Vector2(190, 58)
[node name="SortDirection" type="Button" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"] [node name="SortDirection" type="Button" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
custom_minimum_size = Vector2(112, 30) custom_minimum_size = Vector2(190, 58)
text = "newest first" text = "newest first"
[node name="SortSpacer" type="Control" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"] [node name="SortSpacer" type="Control" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
@ -219,7 +219,7 @@ texture_filter = 1
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1) theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
theme_override_font_sizes/font_size = 19 theme_override_font_sizes/font_size = 29
horizontal_alignment = 1 horizontal_alignment = 1
[node name="DetailData" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"] [node name="DetailData" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
@ -339,7 +339,7 @@ texture_filter = 1
[node name="BagDetailName" type="Label" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagDetail/Margin/Stack"] [node name="BagDetailName" type="Label" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagDetail/Margin/Stack"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
theme_override_font_sizes/font_size = 19 theme_override_font_sizes/font_size = 29
horizontal_alignment = 1 horizontal_alignment = 1
[node name="BagDetailData" type="Label" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagDetail/Margin/Stack"] [node name="BagDetailData" type="Label" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagDetail/Margin/Stack"]
@ -389,10 +389,10 @@ anchor_left = 0.5
anchor_top = 0.5 anchor_top = 0.5
anchor_right = 0.5 anchor_right = 0.5
anchor_bottom = 0.5 anchor_bottom = 0.5
offset_left = -230.0 offset_left = -320.0
offset_top = -105.0 offset_top = -160.0
offset_right = 230.0 offset_right = 320.0
offset_bottom = 105.0 offset_bottom = 160.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
@ -422,10 +422,10 @@ unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "confirm" text = "confirm"
theme_type_variation = &"DangerButton" theme_type_variation = &"DangerButton"
custom_minimum_size = Vector2(120, 38) custom_minimum_size = Vector2(170, 64)
[node name="CancelSaleButton" type="Button" parent="MenuPanel/Margin/Layout/Content/SaleConfirmation/ConfirmationMargin/ConfirmationLayout/ConfirmationButtons"] [node name="CancelSaleButton" type="Button" parent="MenuPanel/Margin/Layout/Content/SaleConfirmation/ConfirmationMargin/ConfirmationLayout/ConfirmationButtons"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "cancel" text = "cancel"
custom_minimum_size = Vector2(120, 38) custom_minimum_size = Vector2(170, 64)

View file

@ -1,12 +1,16 @@
class_name SettingsPanel class_name SettingsPanel
extends PanelContainer extends PanelContainer
const MAX_PANEL_SIZE: Vector2 = Vector2(720.0, 600.0)
const PANEL_EDGE_MARGIN: float = 16.0
const SettingsManagerType = preload( const SettingsManagerType = preload(
"res://settings/player_settings_manager.gd" "res://settings/player_settings_manager.gd"
) )
signal applied signal applied
signal closed signal closed
signal crisp_reset_focus_requested
signal panel_visibility_changed(is_visible: bool)
@onready var _auto_click_toggle: CheckButton = %AutoClickToggle @onready var _auto_click_toggle: CheckButton = %AutoClickToggle
@onready var _auto_click_interval: HSlider = %AutoClickInterval @onready var _auto_click_interval: HSlider = %AutoClickInterval
@ -16,9 +20,15 @@ signal closed
@onready var _controller_sensitivity: HSlider = %ControllerSensitivity @onready var _controller_sensitivity: HSlider = %ControllerSensitivity
@onready var _controller_sensitivity_value: Label = %ControllerSensitivityValue @onready var _controller_sensitivity_value: Label = %ControllerSensitivityValue
@onready var _invert_y_toggle: CheckButton = %InvertYToggle @onready var _invert_y_toggle: CheckButton = %InvertYToggle
@onready var _world_pixel_size: HSlider = %WorldPixelSize
@onready var _world_pixel_size_value: Label = %WorldPixelSizeValue
@onready var _ui_pixel_size: HSlider = %UIPixelSize
@onready var _ui_pixel_size_value: Label = %UIPixelSizeValue
@onready var _feedback: Label = %SettingsFeedback @onready var _feedback: Label = %SettingsFeedback
var _settings_manager: SettingsManagerType var _settings_manager: SettingsManagerType
var _loading_controls: bool = false
var _effective_ui_pixel_size: int = PlayerSettings.DEFAULT_UI_PIXEL_SIZE
func _ready() -> void: func _ready() -> void:
@ -27,6 +37,14 @@ func _ready() -> void:
_auto_click_interval.value_changed.connect(_refresh_value_labels) _auto_click_interval.value_changed.connect(_refresh_value_labels)
_mouse_sensitivity.value_changed.connect(_refresh_value_labels) _mouse_sensitivity.value_changed.connect(_refresh_value_labels)
_controller_sensitivity.value_changed.connect(_refresh_value_labels) _controller_sensitivity.value_changed.connect(_refresh_value_labels)
_world_pixel_size.value_changed.connect(_on_pixel_size_changed)
_ui_pixel_size.value_changed.connect(_on_pixel_size_changed)
%CancelSettingsButton.gui_input.connect(_on_back_button_gui_input)
get_viewport().size_changed.connect(_refresh_panel_size)
var parent_control := get_parent() as Control
if parent_control != null:
parent_control.resized.connect(_refresh_panel_size)
call_deferred("_refresh_panel_size")
func open_panel(settings_manager: SettingsManagerType) -> void: func open_panel(settings_manager: SettingsManagerType) -> void:
@ -34,6 +52,7 @@ func open_panel(settings_manager: SettingsManagerType) -> void:
_load_controls() _load_controls()
_feedback.text = "" _feedback.text = ""
show() show()
panel_visibility_changed.emit(true)
_auto_click_toggle.grab_focus() _auto_click_toggle.grab_focus()
@ -41,6 +60,7 @@ func close_panel() -> void:
if not visible: if not visible:
return return
hide() hide()
panel_visibility_changed.emit(false)
_load_controls() _load_controls()
closed.emit() closed.emit()
@ -55,8 +75,11 @@ func _apply_settings() -> void:
edited.mouse_camera_sensitivity = float(_mouse_sensitivity.value) edited.mouse_camera_sensitivity = float(_mouse_sensitivity.value)
edited.controller_camera_sensitivity = float(_controller_sensitivity.value) edited.controller_camera_sensitivity = float(_controller_sensitivity.value)
edited.invert_camera_y = _invert_y_toggle.button_pressed edited.invert_camera_y = _invert_y_toggle.button_pressed
edited.world_pixel_size = roundi(_world_pixel_size.value)
edited.ui_pixel_size = roundi(_ui_pixel_size.value)
if _settings_manager.apply_settings(edited): if _settings_manager.apply_settings(edited):
hide() hide()
panel_visibility_changed.emit(false)
applied.emit() applied.emit()
else: else:
_feedback.text = "failed to save settings." _feedback.text = "failed to save settings."
@ -65,12 +88,16 @@ func _apply_settings() -> void:
func _load_controls() -> void: func _load_controls() -> void:
if _settings_manager == null or not is_node_ready(): if _settings_manager == null or not is_node_ready():
return return
_loading_controls = true
var settings: PlayerSettings = _settings_manager.current_settings var settings: PlayerSettings = _settings_manager.current_settings
_auto_click_toggle.button_pressed = settings.auto_click_enabled _auto_click_toggle.button_pressed = settings.auto_click_enabled
_auto_click_interval.value = settings.auto_click_interval _auto_click_interval.value = settings.auto_click_interval
_mouse_sensitivity.value = settings.mouse_camera_sensitivity _mouse_sensitivity.value = settings.mouse_camera_sensitivity
_controller_sensitivity.value = settings.controller_camera_sensitivity _controller_sensitivity.value = settings.controller_camera_sensitivity
_invert_y_toggle.button_pressed = settings.invert_camera_y _invert_y_toggle.button_pressed = settings.invert_camera_y
_world_pixel_size.value = settings.world_pixel_size
_ui_pixel_size.value = settings.ui_pixel_size
_loading_controls = false
_refresh_value_labels(0.0) _refresh_value_labels(0.0)
@ -78,3 +105,83 @@ func _refresh_value_labels(_unused: float) -> void:
_auto_click_interval_value.text = "%.2f s" % _auto_click_interval.value _auto_click_interval_value.text = "%.2f s" % _auto_click_interval.value
_mouse_sensitivity_value.text = "%.4f" % _mouse_sensitivity.value _mouse_sensitivity_value.text = "%.4f" % _mouse_sensitivity.value
_controller_sensitivity_value.text = "%.1f" % _controller_sensitivity.value _controller_sensitivity_value.text = "%.1f" % _controller_sensitivity.value
_world_pixel_size_value.text = _pixel_size_label(
roundi(_world_pixel_size.value),
true
)
_ui_pixel_size_value.text = _pixel_size_label(
roundi(_ui_pixel_size.value),
false
)
func _on_pixel_size_changed(_unused: float) -> void:
_refresh_value_labels(0.0)
if _loading_controls or _settings_manager == null:
return
if not _settings_manager.apply_pixelation(
roundi(_world_pixel_size.value),
roundi(_ui_pixel_size.value)
):
_feedback.text = "failed to save pixelation settings."
func _pixel_size_label(pixel_size: int, _is_world: bool) -> String:
var names: PackedStringArray = [
"legible",
"cute",
"retro",
"hardcore",
"wtf",
]
var index: int = clampi(pixel_size - 1, 0, names.size() - 1)
return names[index]
func set_effective_ui_pixel_size(pixel_size: int) -> void:
_effective_ui_pixel_size = clampi(
pixel_size,
PlayerSettings.MIN_UI_PIXEL_SIZE,
PlayerSettings.MAX_UI_PIXEL_SIZE
)
if is_node_ready():
_refresh_value_labels(0.0)
func focus_back_button() -> void:
%CancelSettingsButton.grab_focus()
func refresh_open_panel() -> void:
if visible:
_load_controls()
func _on_back_button_gui_input(event: InputEvent) -> void:
if (
event.is_action_pressed("ui_down")
or event.is_action_pressed("ui_focus_next")
):
crisp_reset_focus_requested.emit()
accept_event()
func _refresh_panel_size() -> void:
if not is_node_ready():
return
var parent_control := get_parent() as Control
if parent_control == null:
return
var available_size: Vector2 = parent_control.size - Vector2.ONE * (
PANEL_EDGE_MARGIN * 2.0
)
var target_size := Vector2(
minf(MAX_PANEL_SIZE.x, maxf(320.0, available_size.x)),
minf(MAX_PANEL_SIZE.y, maxf(240.0, available_size.y))
)
custom_minimum_size = target_size
if parent_control is CenterContainer:
return
set_anchors_preset(Control.PRESET_CENTER)
position = (parent_control.size - target_size) * 0.5
size = target_size

View file

@ -5,7 +5,7 @@
[node name="SettingsPanel" type="PanelContainer"] [node name="SettingsPanel" type="PanelContainer"]
visible = false visible = false
custom_minimum_size = Vector2(600, 490) custom_minimum_size = Vector2(720, 600)
theme = ExtResource("2_theme") theme = ExtResource("2_theme")
script = ExtResource("1_script") script = ExtResource("1_script")
@ -16,35 +16,42 @@ theme_override_constants/margin_top = 24
theme_override_constants/margin_right = 28 theme_override_constants/margin_right = 28
theme_override_constants/margin_bottom = 24 theme_override_constants/margin_bottom = 24
[node name="Content" type="VBoxContainer" parent="Margin"] [node name="Scroll" type="ScrollContainer" parent="Margin"]
layout_mode = 2 layout_mode = 2
horizontal_scroll_mode = 0
[node name="Content" type="VBoxContainer" parent="Margin/Scroll"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 12 theme_override_constants/separation = 12
[node name="Heading" type="Label" parent="Margin/Content"] [node name="Heading" type="Label" parent="Margin/Scroll/Content"]
layout_mode = 2 layout_mode = 2
theme_override_font_sizes/font_size = 26 theme_override_font_sizes/font_size = 39
text = "settings" text = "settings"
horizontal_alignment = 1 horizontal_alignment = 1
[node name="AutoClickToggle" type="CheckButton" parent="Margin/Content"] [node name="AutoClickToggle" type="CheckButton" parent="Margin/Scroll/Content"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "accessibility auto-click" text = "accessibility auto-click"
[node name="AutoClickHelp" type="Label" parent="Margin/Content"] [node name="AutoClickHelp" type="Label" parent="Margin/Scroll/Content"]
layout_mode = 2 layout_mode = 2
text = "lower intervals click barriers faster while held." text = "lower intervals click barriers faster while held."
autowrap_mode = 2
[node name="AutoClickRow" type="HBoxContainer" parent="Margin/Content"] [node name="AutoClickRow" type="HBoxContainer" parent="Margin/Scroll/Content"]
layout_mode = 2 layout_mode = 2
theme_override_constants/separation = 12 theme_override_constants/separation = 12
[node name="Label" type="Label" parent="Margin/Content/AutoClickRow"] [node name="Label" type="Label" parent="Margin/Scroll/Content/AutoClickRow"]
custom_minimum_size = Vector2(190, 0) custom_minimum_size = Vector2(260, 0)
layout_mode = 2 layout_mode = 2
text = "auto-click interval" text = "auto-click interval"
autowrap_mode = 2
[node name="AutoClickInterval" type="HSlider" parent="Margin/Content/AutoClickRow"] [node name="AutoClickInterval" type="HSlider" parent="Margin/Scroll/Content/AutoClickRow"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
@ -53,22 +60,23 @@ max_value = 0.5
step = 0.01 step = 0.01
value = 0.2 value = 0.2
[node name="AutoClickIntervalValue" type="Label" parent="Margin/Content/AutoClickRow"] [node name="AutoClickIntervalValue" type="Label" parent="Margin/Scroll/Content/AutoClickRow"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(62, 0) custom_minimum_size = Vector2(100, 0)
layout_mode = 2 layout_mode = 2
text = "0.20 s" text = "0.20 s"
[node name="MouseRow" type="HBoxContainer" parent="Margin/Content"] [node name="MouseRow" type="HBoxContainer" parent="Margin/Scroll/Content"]
layout_mode = 2 layout_mode = 2
theme_override_constants/separation = 12 theme_override_constants/separation = 12
[node name="Label" type="Label" parent="Margin/Content/MouseRow"] [node name="Label" type="Label" parent="Margin/Scroll/Content/MouseRow"]
custom_minimum_size = Vector2(190, 0) custom_minimum_size = Vector2(260, 0)
layout_mode = 2 layout_mode = 2
text = "mouse camera sensitivity" text = "mouse camera sensitivity"
autowrap_mode = 2
[node name="MouseSensitivity" type="HSlider" parent="Margin/Content/MouseRow"] [node name="MouseSensitivity" type="HSlider" parent="Margin/Scroll/Content/MouseRow"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
@ -77,22 +85,23 @@ max_value = 0.012
step = 0.0005 step = 0.0005
value = 0.005 value = 0.005
[node name="MouseSensitivityValue" type="Label" parent="Margin/Content/MouseRow"] [node name="MouseSensitivityValue" type="Label" parent="Margin/Scroll/Content/MouseRow"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(62, 0) custom_minimum_size = Vector2(100, 0)
layout_mode = 2 layout_mode = 2
text = "0.0050" text = "0.0050"
[node name="ControllerRow" type="HBoxContainer" parent="Margin/Content"] [node name="ControllerRow" type="HBoxContainer" parent="Margin/Scroll/Content"]
layout_mode = 2 layout_mode = 2
theme_override_constants/separation = 12 theme_override_constants/separation = 12
[node name="Label" type="Label" parent="Margin/Content/ControllerRow"] [node name="Label" type="Label" parent="Margin/Scroll/Content/ControllerRow"]
custom_minimum_size = Vector2(190, 0) custom_minimum_size = Vector2(260, 0)
layout_mode = 2 layout_mode = 2
text = "controller camera sensitivity" text = "controller camera sensitivity"
autowrap_mode = 2
[node name="ControllerSensitivity" type="HSlider" parent="Margin/Content/ControllerRow"] [node name="ControllerSensitivity" type="HSlider" parent="Margin/Scroll/Content/ControllerRow"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
@ -101,36 +110,86 @@ max_value = 5.0
step = 0.1 step = 0.1
value = 2.5 value = 2.5
[node name="ControllerSensitivityValue" type="Label" parent="Margin/Content/ControllerRow"] [node name="ControllerSensitivityValue" type="Label" parent="Margin/Scroll/Content/ControllerRow"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(62, 0) custom_minimum_size = Vector2(100, 0)
layout_mode = 2 layout_mode = 2
text = "2.5" text = "2.5"
[node name="InvertYToggle" type="CheckButton" parent="Margin/Content"] [node name="InvertYToggle" type="CheckButton" parent="Margin/Scroll/Content"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "invert vertical camera" text = "invert vertical camera"
[node name="SettingsFeedback" type="Label" parent="Margin/Content"] [node name="WorldPixelRow" type="HBoxContainer" parent="Margin/Scroll/Content"]
layout_mode = 2
theme_override_constants/separation = 12
[node name="Label" type="Label" parent="Margin/Scroll/Content/WorldPixelRow"]
custom_minimum_size = Vector2(260, 0)
layout_mode = 2
text = "world pixelation"
autowrap_mode = 2
[node name="WorldPixelSize" type="HSlider" parent="Margin/Scroll/Content/WorldPixelRow"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 28) layout_mode = 2
size_flags_horizontal = 3
min_value = 1.0
max_value = 5.0
step = 1.0
value = 3.0
[node name="WorldPixelSizeValue" type="Label" parent="Margin/Scroll/Content/WorldPixelRow"]
unique_name_in_owner = true
custom_minimum_size = Vector2(170, 0)
layout_mode = 2
text = "retro"
[node name="UIPixelRow" type="HBoxContainer" parent="Margin/Scroll/Content"]
layout_mode = 2
theme_override_constants/separation = 12
[node name="Label" type="Label" parent="Margin/Scroll/Content/UIPixelRow"]
custom_minimum_size = Vector2(260, 0)
layout_mode = 2
text = "ui pixelation"
autowrap_mode = 2
[node name="UIPixelSize" type="HSlider" parent="Margin/Scroll/Content/UIPixelRow"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
min_value = 1.0
max_value = 5.0
step = 1.0
value = 3.0
[node name="UIPixelSizeValue" type="Label" parent="Margin/Scroll/Content/UIPixelRow"]
unique_name_in_owner = true
custom_minimum_size = Vector2(170, 0)
layout_mode = 2
text = "retro"
[node name="SettingsFeedback" type="Label" parent="Margin/Scroll/Content"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 44)
layout_mode = 2 layout_mode = 2
horizontal_alignment = 1 horizontal_alignment = 1
[node name="Buttons" type="HBoxContainer" parent="Margin/Content"] [node name="Buttons" type="HBoxContainer" parent="Margin/Scroll/Content"]
layout_mode = 2 layout_mode = 2
theme_override_constants/separation = 12 theme_override_constants/separation = 12
alignment = 2 alignment = 2
[node name="ApplySettingsButton" type="Button" parent="Margin/Content/Buttons"] [node name="ApplySettingsButton" type="Button" parent="Margin/Scroll/Content/Buttons"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(120, 42) custom_minimum_size = Vector2(150, 64)
layout_mode = 2 layout_mode = 2
text = "apply" text = "apply"
[node name="CancelSettingsButton" type="Button" parent="Margin/Content/Buttons"] [node name="CancelSettingsButton" type="Button" parent="Margin/Scroll/Content/Buttons"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(120, 42) custom_minimum_size = Vector2(150, 64)
layout_mode = 2 layout_mode = 2
text = "back" text = "back"

View file

@ -7,6 +7,12 @@ const SettingsManagerType = preload(
"res://settings/player_settings_manager.gd" "res://settings/player_settings_manager.gd"
) )
const SettingsPanelType = preload("res://ui/settings_panel.gd") const SettingsPanelType = preload("res://ui/settings_panel.gd")
const BubbleButtonType = preload(
"res://ui/components/bubble_menu/bubble_button.gd"
)
const BubbleClusterType = preload(
"res://ui/components/bubble_menu/bubble_cluster.gd"
)
const DECORATIVE_FISH_TEXTURES: Array[Texture2D] = [ const DECORATIVE_FISH_TEXTURES: Array[Texture2D] = [
preload("res://fish/species/bass/fish_bass_striped.png"), preload("res://fish/species/bass/fish_bass_striped.png"),
preload("res://fish/species/bluegill/fish_bluegill.png"), preload("res://fish/species/bluegill/fish_bluegill.png"),
@ -45,14 +51,19 @@ const BUBBLE_WOBBLE_MAX: float = 9.0
const BUBBLE_EDGE_MARGIN: float = 16.0 const BUBBLE_EDGE_MARGIN: float = 16.0
const MAX_DECORATIVE_BUBBLES: int = 6 const MAX_DECORATIVE_BUBBLES: int = 6
const LOGO_ASPECT_RATIO: float = 2560.0 / 760.0 const LOGO_ASPECT_RATIO: float = 2560.0 / 760.0
const LOGO_WIDTH_FACTOR: float = 0.52 const LOGO_WIDTH_FACTOR: float = 0.4784
const LOGO_MIN_WIDTH: float = 320.0 const LOGO_MIN_WIDTH: float = 294.0
const LOGO_MAX_WIDTH: float = 720.0 const LOGO_MAX_WIDTH: float = 662.0
const BUTTON_COLUMN_WIDTH: float = 360.0
const TITLE_HORIZONTAL_MARGIN: float = 48.0 const TITLE_HORIZONTAL_MARGIN: float = 48.0
const BUBBLE_FIELD_MAX_WIDTH: float = 396.0
const BUBBLE_FIELD_DESKTOP_HEIGHT: float = 318.0
const BUBBLE_FIELD_COMPACT_WIDTH: float = 294.0
const BUBBLE_FIELD_COMPACT_HEIGHT: float = 200.0
const BUBBLE_COMPACT_HEIGHT_THRESHOLD: float = 560.0
const START_PROMPT_MIN_SCALE: float = 0.985 const START_PROMPT_MIN_SCALE: float = 0.985
const START_PROMPT_MAX_SCALE: float = 1.015 const START_PROMPT_MAX_SCALE: float = 1.015
const START_PROMPT_CYCLE_SECONDS: float = 3.0 const START_PROMPT_CYCLE_SECONDS: float = 3.0
const DISABLED_BUBBLE_LABEL_ALPHA: float = 0.55
signal gameplay_requested signal gameplay_requested
signal quit_requested signal quit_requested
@ -63,16 +74,19 @@ enum ConfirmationAction {
DELETE_SAVE, DELETE_SAVE,
} }
@onready var _continue_button: Button = %ContinueButton @onready var _continue_button: BubbleButtonType = %ContinueButton
@onready var _new_game_button: Button = %NewGameButton @onready var _new_game_button: BubbleButtonType = %NewGameButton
@onready var _settings_button: Button = %SettingsButton @onready var _settings_button: BubbleButtonType = %SettingsButton
@onready var _delete_button: Button = %DeleteSaveButton @onready var _delete_button: BubbleButtonType = %DeleteSaveButton
@onready var _quit_button: Button = %QuitButton @onready var _quit_button: BubbleButtonType = %QuitButton
@onready var _new_game_label: Label = %NewGameLabel
@onready var _delete_save_label: Label = %DeleteSaveLabel
@onready var _feedback_label: Label = %FeedbackLabel @onready var _feedback_label: Label = %FeedbackLabel
@onready var _confirmation_panel: PanelContainer = %ConfirmationPanel @onready var _confirmation_panel: PanelContainer = %ConfirmationPanel
@onready var _confirmation_text: Label = %ConfirmationText @onready var _confirmation_text: Label = %ConfirmationText
@onready var _confirm_button: Button = %ConfirmButton @onready var _confirm_button: Button = %ConfirmButton
@onready var _settings_panel: SettingsPanelType = %SettingsPanel @onready var _settings_panel: SettingsPanelType = %SettingsPanel
@onready var _background: ColorRect = %Background
@onready var _decorative_fish_layer: Control = %DecorativeFishLayer @onready var _decorative_fish_layer: Control = %DecorativeFishLayer
@onready var _decorative_fish_timer: Timer = %DecorativeFishTimer @onready var _decorative_fish_timer: Timer = %DecorativeFishTimer
@onready var _decorative_bubble_layer: Control = %DecorativeBubbleLayer @onready var _decorative_bubble_layer: Control = %DecorativeBubbleLayer
@ -80,7 +94,7 @@ enum ConfirmationAction {
@onready var _decorative_bubble_cluster_timer: Timer = %DecorativeBubbleClusterTimer @onready var _decorative_bubble_cluster_timer: Timer = %DecorativeBubbleClusterTimer
@onready var _title_logo: TextureRect = %TitleLogo @onready var _title_logo: TextureRect = %TitleLogo
@onready var _button_center: CenterContainer = %ButtonCenter @onready var _button_center: CenterContainer = %ButtonCenter
@onready var _button_stack: VBoxContainer = %ButtonStack @onready var _bubble_field: BubbleClusterType = %BubbleField
@onready var _start_prompt_center: CenterContainer = %StartPromptCenter @onready var _start_prompt_center: CenterContainer = %StartPromptCenter
@onready var _start_prompt_label: Label = %StartPromptLabel @onready var _start_prompt_label: Label = %StartPromptLabel
@ -102,6 +116,7 @@ var _awaiting_start_input: bool = false
var _start_prompt_elapsed: float = 0.0 var _start_prompt_elapsed: float = 0.0
var _navigation_focus_active: bool = false var _navigation_focus_active: bool = false
var _modal_restore_navigation_focus: bool = false var _modal_restore_navigation_focus: bool = false
var _world_pixel_size: int = PlayerSettings.DEFAULT_WORLD_PIXEL_SIZE
func _ready() -> void: func _ready() -> void:
@ -114,6 +129,7 @@ func _ready() -> void:
%CancelConfirmButton.pressed.connect(_close_confirmation) %CancelConfirmButton.pressed.connect(_close_confirmation)
_settings_panel.applied.connect(_on_settings_applied) _settings_panel.applied.connect(_on_settings_applied)
_settings_panel.closed.connect(_on_settings_closed) _settings_panel.closed.connect(_on_settings_closed)
_bubble_field.configure(_get_title_buttons())
_decorative_fish_timer.timeout.connect(_on_decorative_fish_timer_timeout) _decorative_fish_timer.timeout.connect(_on_decorative_fish_timer_timeout)
_decorative_bubble_event_timer.timeout.connect( _decorative_bubble_event_timer.timeout.connect(
_on_decorative_bubble_event_timer_timeout _on_decorative_bubble_event_timer_timeout
@ -197,31 +213,106 @@ func _update_title_layout() -> void:
logo_width, logo_width,
logo_width / LOGO_ASPECT_RATIO logo_width / LOGO_ASPECT_RATIO
) )
_button_stack.custom_minimum_size = Vector2( var field_width: float = minf(BUBBLE_FIELD_MAX_WIDTH, available_width)
minf(BUTTON_COLUMN_WIDTH, available_width), var field_height: float = (
0.0 BUBBLE_FIELD_COMPACT_HEIGHT
if size.y < BUBBLE_COMPACT_HEIGHT_THRESHOLD
else BUBBLE_FIELD_DESKTOP_HEIGHT
)
if size.y < BUBBLE_COMPACT_HEIGHT_THRESHOLD:
field_width = minf(BUBBLE_FIELD_COMPACT_WIDTH, available_width)
_bubble_field.custom_minimum_size = Vector2(field_width, field_height)
var compact_layout: bool = field_height == BUBBLE_FIELD_COMPACT_HEIGHT
_bubble_field.apply_layout(
Vector2(field_width, field_height),
compact_layout
)
_new_game_label.text = "new\ngame" if compact_layout else "new game"
_delete_save_label.text = (
"delete\nsave" if compact_layout else "delete save"
)
_update_world_preview_resolution()
func _get_title_buttons() -> Array[BubbleButton]:
return [
_continue_button,
_new_game_button,
_settings_button,
_delete_button,
_quit_button,
]
func set_world_pixelation(pixel_size: int) -> void:
_world_pixel_size = clampi(
pixel_size,
PlayerSettings.MIN_WORLD_PIXEL_SIZE,
PlayerSettings.MAX_WORLD_PIXEL_SIZE
)
if is_node_ready():
_update_world_preview_resolution()
func _update_world_preview_resolution() -> void:
var render_scale: float = PlayerSettings.get_world_render_scale(
_world_pixel_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)
)
)
var logo_material := _title_logo.material as ShaderMaterial
if logo_material != null:
var effect_scale: float = 1.0 / render_scale
logo_material.set_shader_parameter(
"horizontal_displacement_pixels",
minf(2.0, 1.5 * effect_scale)
)
logo_material.set_shader_parameter(
"bob_amount_pixels",
minf(4.0, 3.0 * effect_scale)
)
func _snap_world_preview(value: Vector2) -> Vector2:
var render_scale: float = PlayerSettings.get_world_render_scale(
_world_pixel_size
)
var step: float = 1.0 / render_scale
return Vector2(
roundf(value.x / step) * step,
roundf(value.y / step) * step
) )
func _process(delta: float) -> void: func _process(delta: float) -> void:
if ( if not visible:
not _awaiting_start_input
or not visible
or not _start_prompt_center.visible
):
return return
if _awaiting_start_input and _start_prompt_center.visible:
_start_prompt_elapsed = fmod( _start_prompt_elapsed = fmod(
_start_prompt_elapsed + delta, _start_prompt_elapsed + delta,
START_PROMPT_CYCLE_SECONDS START_PROMPT_CYCLE_SECONDS
) )
var phase: float = _start_prompt_elapsed / START_PROMPT_CYCLE_SECONDS var phase: float = (
var pulse_weight: float = (sin(phase * TAU - PI * 0.5) + 1.0) * 0.5 _start_prompt_elapsed / START_PROMPT_CYCLE_SECONDS
)
var pulse_weight: float = (
sin(phase * TAU - PI * 0.5) + 1.0
) * 0.5
var prompt_scale: float = lerpf( var prompt_scale: float = lerpf(
START_PROMPT_MIN_SCALE, START_PROMPT_MIN_SCALE,
START_PROMPT_MAX_SCALE, START_PROMPT_MAX_SCALE,
pulse_weight pulse_weight
) )
_start_prompt_label.scale = Vector2.ONE * prompt_scale _start_prompt_label.scale = Vector2.ONE * prompt_scale
if _button_center.visible:
_bubble_field.advance_motion(delta)
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
@ -337,7 +428,7 @@ func _start_entry_prompt_animation() -> void:
func _stop_entry_prompt_animation() -> void: func _stop_entry_prompt_animation() -> void:
set_process(false) set_process(visible and _button_center.visible)
_start_prompt_elapsed = 0.0 _start_prompt_elapsed = 0.0
_start_prompt_label.scale = Vector2.ONE _start_prompt_label.scale = Vector2.ONE
@ -356,6 +447,7 @@ func _reveal_primary_menu() -> void:
_start_prompt_center.hide() _start_prompt_center.hide()
_button_center.show() _button_center.show()
_feedback_label.show() _feedback_label.show()
set_process(true)
_navigation_focus_active = false _navigation_focus_active = false
_release_title_focus() _release_title_focus()
@ -506,6 +598,9 @@ func _refresh_save_inspection() -> void:
_inspection = _save_manager.inspect_save() _inspection = _save_manager.inspect_save()
_continue_button.disabled = not _inspection.can_continue() _continue_button.disabled = not _inspection.can_continue()
_delete_button.disabled = not _inspection.can_delete() _delete_button.disabled = not _inspection.can_delete()
_delete_save_label.modulate.a = (
DISABLED_BUBBLE_LABEL_ALPHA if _delete_button.disabled else 1.0
)
_feedback_label.text = _inspection.message _feedback_label.text = _inspection.message
if _inspection.status == SaveInspectionType.Status.VALID_SUPPORTED: if _inspection.status == SaveInspectionType.Status.VALID_SUPPORTED:
_feedback_label.text = ( _feedback_label.text = (
@ -546,6 +641,7 @@ func _on_title_visibility_changed() -> void:
if not _decorative_presentation_ready: if not _decorative_presentation_ready:
return return
if visible: if visible:
set_process(true)
_start_decorative_presentation() _start_decorative_presentation()
else: else:
_stop_entry_prompt_animation() _stop_entry_prompt_animation()
@ -742,6 +838,7 @@ func _update_decorative_fish(
8.0, 8.0,
maxf(8.0, _decorative_fish_layer.size.y - fish_control.size.y - 8.0) maxf(8.0, _decorative_fish_layer.size.y - fish_control.size.y - 8.0)
) )
fish_control.position = _snap_world_preview(fish_control.position)
func _on_decorative_fish_finished( func _on_decorative_fish_finished(
@ -944,13 +1041,13 @@ func _update_decorative_bubble(
var wobble: float = sin( var wobble: float = sin(
wobble_phase + progress * TAU * wobble_cycles wobble_phase + progress * TAU * wobble_cycles
) * wobble_amplitude ) * wobble_amplitude
bubble.position = Vector2( bubble.position = _snap_world_preview(Vector2(
base_x base_x
+ horizontal_drift * progress + horizontal_drift * progress
+ wobble + wobble
- bubble.size.x * 0.5, - bubble.size.x * 0.5,
lerpf(start_y, end_y, progress) lerpf(start_y, end_y, progress)
) ))
func _on_decorative_bubble_finished( func _on_decorative_bubble_finished(

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=9 format=3] [gd_scene load_steps=12 format=3]
[ext_resource type="Script" path="res://ui/title_screen.gd" id="1_script"] [ext_resource type="Script" path="res://ui/title_screen.gd" id="1_script"]
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"] [ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
@ -6,6 +6,9 @@
[ext_resource type="Shader" path="res://ui/title_water_background.gdshader" id="4_water_shader"] [ext_resource type="Shader" path="res://ui/title_water_background.gdshader" id="4_water_shader"]
[ext_resource type="Texture2D" path="res://ui/assets/title/netfishing_logo.png" id="5_logo"] [ext_resource type="Texture2D" path="res://ui/assets/title/netfishing_logo.png" id="5_logo"]
[ext_resource type="Shader" path="res://ui/title_logo_underwater.gdshader" id="6_logo_shader"] [ext_resource type="Shader" path="res://ui/title_logo_underwater.gdshader" id="6_logo_shader"]
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_button.gd" id="7_bubble_button"]
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_cluster.gd" id="8_bubble_cluster"]
[ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="9_bubble_profile"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water"]
shader = ExtResource("4_water_shader") shader = ExtResource("4_water_shader")
@ -27,6 +30,7 @@ theme = ExtResource("2_theme")
script = ExtResource("1_script") script = ExtResource("1_script")
[node name="Background" type="ColorRect" parent="."] [node name="Background" type="ColorRect" parent="."]
unique_name_in_owner = true
material = SubResource("ShaderMaterial_title_water") material = SubResource("ShaderMaterial_title_water")
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
@ -92,7 +96,7 @@ mouse_filter = 2
theme_override_colors/font_color = Color(1, 1, 1, 1) theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_colors/font_outline_color = Color(0.02, 0.075, 0.11, 0.9) theme_override_colors/font_outline_color = Color(0.02, 0.075, 0.11, 0.9)
theme_override_constants/outline_size = 3 theme_override_constants/outline_size = 3
theme_override_font_sizes/font_size = 26 theme_override_font_sizes/font_size = 39
text = "press any key to start" text = "press any key to start"
horizontal_alignment = 1 horizontal_alignment = 1
vertical_alignment = 1 vertical_alignment = 1
@ -108,7 +112,7 @@ grow_vertical = 2
[node name="MainContent" type="VBoxContainer" parent="Center"] [node name="MainContent" type="VBoxContainer" parent="Center"]
layout_mode = 2 layout_mode = 2
theme_override_constants/separation = 8 theme_override_constants/separation = 7
alignment = 1 alignment = 1
[node name="TitleLogo" type="TextureRect" parent="Center/MainContent"] [node name="TitleLogo" type="TextureRect" parent="Center/MainContent"]
@ -125,12 +129,12 @@ stretch_mode = 5
[node name="PlaytestLabel" type="Label" parent="Center/MainContent"] [node name="PlaytestLabel" type="Label" parent="Center/MainContent"]
layout_mode = 2 layout_mode = 2
theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1) theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1)
theme_override_font_sizes/font_size = 13 theme_override_font_sizes/font_size = 17
text = "pre-alpha playtest 0.2" text = "pre-alpha playtest 0.2"
horizontal_alignment = 1 horizontal_alignment = 1
[node name="Spacer" type="Control" parent="Center/MainContent"] [node name="Spacer" type="Control" parent="Center/MainContent"]
custom_minimum_size = Vector2(0, 4) custom_minimum_size = Vector2(0, 3)
layout_mode = 2 layout_mode = 2
[node name="ButtonCenter" type="CenterContainer" parent="Center/MainContent"] [node name="ButtonCenter" type="CenterContainer" parent="Center/MainContent"]
@ -138,46 +142,151 @@ unique_name_in_owner = true
visible = false visible = false
layout_mode = 2 layout_mode = 2
[node name="ButtonStack" type="VBoxContainer" parent="Center/MainContent/ButtonCenter"] [node name="BubbleField" type="Control" parent="Center/MainContent/ButtonCenter"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(360, 0) custom_minimum_size = Vector2(396, 318)
layout_mode = 2 layout_mode = 2
theme_override_constants/separation = 8 script = ExtResource("8_bubble_cluster")
profile = ExtResource("9_bubble_profile")
[node name="ContinueButton" type="Button" parent="Center/MainContent/ButtonCenter/ButtonStack"] [node name="ContinueButton" type="Button" parent="Center/MainContent/ButtonCenter/BubbleField"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 44) offset_left = 123.0
layout_mode = 2 offset_top = 86.0
offset_right = 307.0
offset_bottom = 264.0
text = "continue" text = "continue"
script = ExtResource("7_bubble_button")
profile = ExtResource("9_bubble_profile")
neutral_size = Vector2(184, 178)
desktop_anchor = Vector2(215, 175)
compact_anchor = Vector2(215, 175)
minimum_font_size = 19
maximum_font_size = 30
horizontal_amplitude = 1.8
vertical_amplitude = 4.6
motion_period = 5.8
deformation_amplitude = 0.01
deformation_period = 6.7
[node name="NewGameButton" type="Button" parent="Center/MainContent/ButtonCenter/ButtonStack"] [node name="NewGameButton" type="Button" parent="Center/MainContent/ButtonCenter/BubbleField"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 44) offset_left = 42.0
layout_mode = 2 offset_top = 4.0
offset_right = 170.0
offset_bottom = 126.0
script = ExtResource("7_bubble_button")
profile = ExtResource("9_bubble_profile")
neutral_size = Vector2(128, 122)
desktop_anchor = Vector2(106, 65)
compact_anchor = Vector2(106, 68)
compact_minimum_size = Vector2(84, 84)
label_control_path = NodePath("NewGameLabel")
minimum_font_size = 15
maximum_font_size = 23
horizontal_amplitude = 2.3
vertical_amplitude = 5.5
motion_period = 6.3
motion_phase = 1.2
deformation_amplitude = 0.018
deformation_period = 5.9
[node name="NewGameLabel" type="Label" parent="Center/MainContent/ButtonCenter/BubbleField/NewGameButton"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_colors/font_color = Color(0.035, 0.145, 0.22, 1)
theme_override_constants/line_spacing = -3
text = "new game" text = "new game"
horizontal_alignment = 1
vertical_alignment = 1
[node name="SettingsButton" type="Button" parent="Center/MainContent/ButtonCenter/ButtonStack"] [node name="SettingsButton" type="Button" parent="Center/MainContent/ButtonCenter/BubbleField"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 44) offset_left = 264.0
layout_mode = 2 offset_top = 17.0
offset_right = 384.0
offset_bottom = 133.0
text = "settings" text = "settings"
script = ExtResource("7_bubble_button")
profile = ExtResource("9_bubble_profile")
desktop_anchor = Vector2(324, 75)
compact_anchor = Vector2(324, 68)
compact_minimum_size = Vector2(94, 90)
minimum_font_size = 15
maximum_font_size = 22
vertical_amplitude = 3.7
motion_period = 4.9
motion_phase = 2.5
deformation_period = 6.3
[node name="DeleteSaveButton" type="Button" parent="Center/MainContent/ButtonCenter/ButtonStack"] [node name="DeleteSaveButton" type="Button" parent="Center/MainContent/ButtonCenter/BubbleField"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 44) offset_left = -11.0
layout_mode = 2 offset_top = 122.0
offset_right = 123.0
offset_bottom = 248.0
script = ExtResource("7_bubble_button")
profile = ExtResource("9_bubble_profile")
neutral_size = Vector2(134, 126)
desktop_anchor = Vector2(56, 185)
compact_anchor = Vector2(53, 196)
compact_minimum_size = Vector2(86, 86)
label_control_path = NodePath("DeleteSaveLabel")
minimum_font_size = 15
maximum_font_size = 23
vertical_amplitude = 5.1
motion_period = 5.5
motion_phase = 3.7
deformation_amplitude = 0.018
deformation_period = 5.4
[node name="DeleteSaveLabel" type="Label" parent="Center/MainContent/ButtonCenter/BubbleField/DeleteSaveButton"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_colors/font_color = Color(0.035, 0.145, 0.22, 1)
theme_override_constants/line_spacing = -3
text = "delete save" text = "delete save"
horizontal_alignment = 1
vertical_alignment = 1
[node name="QuitButton" type="Button" parent="Center/MainContent/ButtonCenter/ButtonStack"] [node name="QuitButton" type="Button" parent="Center/MainContent/ButtonCenter/BubbleField"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 44) offset_left = 275.0
layout_mode = 2 offset_top = 217.0
offset_right = 369.0
offset_bottom = 309.0
text = "quit" text = "quit"
script = ExtResource("7_bubble_button")
profile = ExtResource("9_bubble_profile")
neutral_size = Vector2(94, 92)
desktop_anchor = Vector2(322, 263)
compact_anchor = Vector2(322, 263)
compact_minimum_size = Vector2(68, 68)
minimum_font_size = 14
maximum_font_size = 18
horizontal_amplitude = 1.4
vertical_amplitude = 3.2
motion_period = 4.2
motion_phase = 4.8
deformation_amplitude = 0.02
deformation_period = 4.8
[node name="FeedbackLabel" type="Label" parent="Center/MainContent"] [node name="FeedbackLabel" type="Label" parent="Center/MainContent"]
unique_name_in_owner = true unique_name_in_owner = true
visible = false visible = false
custom_minimum_size = Vector2(0, 42) custom_minimum_size = Vector2(0, 44)
layout_mode = 2 layout_mode = 2
text = "" text = ""
horizontal_alignment = 1 horizontal_alignment = 1
@ -194,10 +303,10 @@ anchor_left = 0.5
anchor_top = 0.5 anchor_top = 0.5
anchor_right = 0.5 anchor_right = 0.5
anchor_bottom = 0.5 anchor_bottom = 0.5
offset_left = -230.0 offset_left = -320.0
offset_top = -95.0 offset_top = -150.0
offset_right = 230.0 offset_right = 320.0
offset_bottom = 95.0 offset_bottom = 150.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
@ -226,13 +335,13 @@ alignment = 1
[node name="ConfirmButton" type="Button" parent="ConfirmationPanel/Margin/Content/Buttons"] [node name="ConfirmButton" type="Button" parent="ConfirmationPanel/Margin/Content/Buttons"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(150, 42) custom_minimum_size = Vector2(190, 64)
layout_mode = 2 layout_mode = 2
text = "confirm" text = "confirm"
[node name="CancelConfirmButton" type="Button" parent="ConfirmationPanel/Margin/Content/Buttons"] [node name="CancelConfirmButton" type="Button" parent="ConfirmationPanel/Margin/Content/Buttons"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(120, 42) custom_minimum_size = Vector2(160, 64)
layout_mode = 2 layout_mode = 2
text = "cancel" text = "cancel"
@ -245,9 +354,9 @@ anchor_left = 0.5
anchor_top = 0.5 anchor_top = 0.5
anchor_right = 0.5 anchor_right = 0.5
anchor_bottom = 0.5 anchor_bottom = 0.5
offset_left = -300.0 offset_left = -360.0
offset_top = -245.0 offset_top = -300.0
offset_right = 300.0 offset_right = 360.0
offset_bottom = 245.0 offset_bottom = 300.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2

View file

@ -0,0 +1,111 @@
class_name UIPixelationPresenter
extends SubViewportContainer
const MIN_UI_VIEWPORT_SIZE: Vector2i = Vector2i(320, 180)
signal effective_pixel_size_changed(
requested_pixel_size: int,
effective_pixel_size: int,
)
@onready var _ui_viewport: SubViewport = $UIViewport
@onready var _ui_root: Control = $UIViewport/GameUI/UIRoot
var _requested_pixel_size: int = PlayerSettings.DEFAULT_UI_PIXEL_SIZE
var _effective_pixel_size: int = PlayerSettings.DEFAULT_UI_PIXEL_SIZE
var _gameplay_active: bool = false
var _interactive_ui_open: bool = false
func _ready() -> void:
var root_viewport: Viewport = get_viewport()
root_viewport.size_changed.connect(_resize_presentation)
_resize_presentation()
func set_pixel_size(pixel_size: int) -> void:
_requested_pixel_size = clampi(
pixel_size,
PlayerSettings.MIN_UI_PIXEL_SIZE,
PlayerSettings.MAX_UI_PIXEL_SIZE
)
_resize_presentation()
func get_requested_pixel_size() -> int:
return _requested_pixel_size
func get_effective_pixel_size() -> int:
return _effective_pixel_size
func get_ui_viewport_size() -> Vector2i:
return _ui_viewport.size
func set_gameplay_active(active: bool) -> void:
_gameplay_active = active
_refresh_mouse_filter()
func set_interactive_ui_open(is_open: bool) -> void:
_interactive_ui_open = is_open
_refresh_mouse_filter()
func _refresh_mouse_filter() -> void:
mouse_filter = (
Control.MOUSE_FILTER_STOP
if not _gameplay_active or _interactive_ui_open
else Control.MOUSE_FILTER_IGNORE
)
func _resize_presentation() -> void:
if not is_node_ready():
return
var root_size: Vector2i = get_window().size
if root_size.x <= 0 or root_size.y <= 0:
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(
ceili(float(root_size.x) * render_scale),
ceili(float(root_size.y) * render_scale)
)
var presentation_size: Vector2 = Vector2(viewport_size) / render_scale
set_anchors_preset(Control.PRESET_TOP_LEFT)
position = (Vector2(root_size) - presentation_size) * 0.5
size = Vector2(viewport_size)
scale = Vector2.ONE / render_scale
stretch_shrink = 1
_ui_root.position = Vector2.ZERO
_ui_root.scale = Vector2.ONE * render_scale
_ui_root.size = Vector2(root_size)
if (
previous_effective_size != _effective_pixel_size
or _requested_pixel_size != _effective_pixel_size
):
effective_pixel_size_changed.emit(
_requested_pixel_size,
_effective_pixel_size
)
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)
)
if (
candidate_size.x >= MIN_UI_VIEWPORT_SIZE.x
and candidate_size.y >= MIN_UI_VIEWPORT_SIZE.y
):
return candidate
return PlayerSettings.MIN_UI_PIXEL_SIZE

View file

@ -0,0 +1 @@
uid://bdtb05vkiicd