Rebrand project and refresh title presentation

This commit is contained in:
Alexander Sellite 2026-08-25 17:14:12 -04:00
parent 17fef2c5d4
commit 7ae20789a9
138 changed files with 1511 additions and 1498 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dwe5p5t0ode73"
path.s3tc="res://.godot/imported/straywild_logo.png-de66accac60dbd31604d2c39ab8d2775.s3tc.ctex"
path.etc2="res://.godot/imported/straywild_logo.png-de66accac60dbd31604d2c39ab8d2775.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://ui/assets/title/straywild_logo.png"
dest_files=["res://.godot/imported/straywild_logo.png-de66accac60dbd31604d2c39ab8d2775.s3tc.ctex", "res://.godot/imported/straywild_logo.png-de66accac60dbd31604d2c39ab8d2775.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

View file

@ -2,6 +2,13 @@ class_name BubbleButton
extends Button
const ICON_FILL_RATIO: float = 0.76
const LABELED_ICON_FILL_RATIO: float = 0.58
const BubbleGradientBackgroundType = preload(
"res://ui/components/bubble_menu/bubble_gradient_background.gd"
)
const BubbleHoverDrawerType = preload(
"res://ui/components/bubble_menu/bubble_hover_drawer.gd"
)
@export var profile: BubbleMenuProfile
@ -16,6 +23,9 @@ const ICON_FILL_RATIO: float = 0.76
@export_range(1, 256, 1) var minimum_font_size: int = 14
@export_range(1, 256, 1) var maximum_font_size: int = 32
@export_group("Icon")
@export_range(0.0, 1.0, 0.01) var icon_fill_ratio_override: float = 0.0
@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
@ -23,20 +33,37 @@ const ICON_FILL_RATIO: float = 0.76
@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
@export var hover_wiggle_amplitude: Vector2 = Vector2.ZERO
@export_range(0.1, 30.0, 0.1) var hover_wiggle_period: float = 2.0
@export_group("Hover Drawer")
@export var hover_drawer_text: String = ""
@export_enum("Right", "Left Vertical") var hover_drawer_mode: int = 0
@export_range(0.1, 12.0, 0.1) var hover_drawer_speed: float = 4.0
@export_group("Hit Area")
@export var elliptical_hit_area: bool = true
var neutral_position: Vector2 = Vector2.ZERO
var presented_size: Vector2 = Vector2.ZERO
var emphasis: float = 0.0
var _hovered: bool = false
var _drawer_focused: bool = false
var _drawer_reveal: float = 0.0
var _gradient_background: BubbleGradientBackground
var _hover_drawer: BubbleHoverDrawer
func _ready() -> void:
mouse_entered.connect(_set_hovered.bind(true))
mouse_exited.connect(_set_hovered.bind(false))
focus_entered.connect(_set_drawer_focused.bind(true))
focus_exited.connect(_set_drawer_focused.bind(false))
resized.connect(_update_pivot)
_update_pivot()
apply_profile()
_apply_icon_presentation(neutral_size)
_update_hover_drawer()
func _gui_input(event: InputEvent) -> void:
@ -97,6 +124,8 @@ func apply_layout(
presented_size = bubble_size
_update_pivot()
_apply_icon_presentation(bubble_size)
if _hover_drawer != null:
_hover_drawer.refresh_layout()
var label_control: Control = get_label_control()
var font_size := clampi(
roundi(minf(bubble_size.x, bubble_size.y) * font_size_ratio),
@ -134,17 +163,26 @@ func _uses_icon_only_presentation() -> bool:
func _apply_icon_presentation(bubble_size: Vector2) -> void:
if icon == null:
return
vertical_icon_alignment = VERTICAL_ALIGNMENT_CENTER
expand_icon = true
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
var fill_ratio: float = (
ICON_FILL_RATIO
if _uses_icon_only_presentation()
else LABELED_ICON_FILL_RATIO
)
if _uses_icon_only_presentation() and icon_fill_ratio_override > 0.0:
fill_ratio = icon_fill_ratio_override
add_theme_constant_override(
"icon_max_width",
maxi(1, roundi(minf(bubble_size.x, bubble_size.y) * fill_ratio)),
)
if not _uses_icon_only_presentation():
return
alignment = HORIZONTAL_ALIGNMENT_CENTER
icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
vertical_icon_alignment = VERTICAL_ALIGNMENT_CENTER
expand_icon = true
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
add_theme_constant_override(
"icon_max_width",
maxi(1, roundi(minf(bubble_size.x, bubble_size.y) * ICON_FILL_RATIO)),
)
func advance_emphasis(delta: float) -> void:
@ -154,6 +192,27 @@ func advance_emphasis(delta: float) -> void:
target,
profile.emphasis_speed * delta
)
if _gradient_background != null:
_gradient_background.queue_redraw()
var drawer_target: float = 1.0 if (_hovered or _drawer_focused) else 0.0
_drawer_reveal = move_toward(
_drawer_reveal,
drawer_target,
hover_drawer_speed * delta,
)
if _hover_drawer != null:
_hover_drawer.set_reveal(_drawer_reveal)
func reset_hover_presentation() -> void:
_hovered = false
_drawer_focused = false
emphasis = 0.0
_drawer_reveal = 0.0
if _gradient_background != null:
_gradient_background.queue_redraw()
if _hover_drawer != null:
_hover_drawer.set_reveal(0.0)
func calculate_target(
@ -169,12 +228,17 @@ func calculate_target(
sin(phase * 0.73 + motion_phase) * horizontal_amplitude,
sin(phase) * vertical_amplitude
) * layout_scale * motion_scale
var hover_phase: float = elapsed / hover_wiggle_period * TAU
var hover_offset := Vector2(
sin(hover_phase),
sin(hover_phase * 2.0 + motion_phase) * 0.5,
) * hover_wiggle_amplitude * emphasis * layout_scale * motion_scale
idle_offset.y -= (
profile.hover_focus_lift
* emphasis
* layout_scale
)
return neutral_position + idle_offset
return neutral_position + idle_offset + hover_offset
func calculate_visual_scale(
@ -219,6 +283,8 @@ func apply_presentation(
func _has_point(point: Vector2) -> bool:
if not elliptical_hit_area:
return Rect2(Vector2.ZERO, size).has_point(point)
var radius: Vector2 = size * 0.5
if radius.x <= 0.0 or radius.y <= 0.0:
return false
@ -228,6 +294,12 @@ func _has_point(point: Vector2) -> bool:
func _set_hovered(value: bool) -> void:
_hovered = value
if _gradient_background != null:
_gradient_background.queue_redraw()
func _set_drawer_focused(value: bool) -> void:
_drawer_focused = value
func _update_pivot() -> void:
@ -237,6 +309,7 @@ func _update_pivot() -> void:
func apply_profile() -> void:
if profile == null:
return
_update_gradient_background()
add_theme_stylebox_override("normal", profile.make_normal_style())
var hover_style: StyleBoxFlat = profile.make_hover_style()
add_theme_stylebox_override("hover", hover_style)
@ -251,3 +324,35 @@ func apply_profile() -> void:
var label_control: Control = get_label_control()
if label_control != self:
label_control.add_theme_color_override("font_color", profile.text_color)
func _update_gradient_background() -> void:
if not profile.gradient_enabled:
if _gradient_background != null:
_gradient_background.queue_free()
_gradient_background = null
return
if _gradient_background == null:
_gradient_background = BubbleGradientBackgroundType.new()
_gradient_background.name = "GradientBackground"
add_child(_gradient_background)
move_child(_gradient_background, 0)
_gradient_background.configure(self, profile)
func _update_hover_drawer() -> void:
if hover_drawer_text.is_empty():
if _hover_drawer != null:
_hover_drawer.queue_free()
_hover_drawer = null
return
if _hover_drawer == null:
_hover_drawer = BubbleHoverDrawerType.new()
_hover_drawer.name = "HoverDrawer"
add_child(_hover_drawer)
move_child(_hover_drawer, 0)
_hover_drawer.configure(
self,
hover_drawer_text,
hover_drawer_mode as BubbleHoverDrawer.DrawerMode,
)

View file

@ -9,6 +9,7 @@ const ControllerFocusNavigationType = preload(
@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
@export var collision_separation_enabled: bool = true
var _bubbles: Array[BubbleButton] = []
var _elapsed: float = 0.0
@ -70,39 +71,40 @@ func advance_motion(delta: float) -> void:
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
if collision_separation_enabled:
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
)
)
targets[first_index] -= correction
targets[second_index] += correction
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
)

View file

@ -0,0 +1,79 @@
class_name BubbleGradientBackground
extends Control
const CORNER_SEGMENTS: int = 8
var host_button: BaseButton
var profile: BubbleMenuProfile
func configure(
button: BaseButton,
menu_profile: BubbleMenuProfile,
) -> void:
host_button = button
profile = menu_profile
mouse_filter = Control.MOUSE_FILTER_IGNORE
show_behind_parent = true
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
queue_redraw()
func _draw() -> void:
if host_button == null or profile == null or size.x <= 0.0 or size.y <= 0.0:
return
var fill_color: Color = _get_fill_color()
var top_color: Color = fill_color.lightened(profile.gradient_top_lift)
var points := PackedVector2Array()
_append_arc(points, Vector2(profile.corner_radius, profile.corner_radius), PI, PI * 1.5)
_append_arc(
points,
Vector2(size.x - profile.corner_radius, profile.corner_radius),
PI * 1.5,
TAU,
)
_append_arc(
points,
Vector2(size.x - profile.corner_radius, size.y - profile.corner_radius),
0.0,
PI * 0.5,
)
_append_arc(
points,
Vector2(profile.corner_radius, size.y - profile.corner_radius),
PI * 0.5,
PI,
)
var colors := PackedColorArray()
for point: Vector2 in points:
colors.append(top_color.lerp(fill_color, clampf(point.y / size.y, 0.0, 1.0)))
draw_polygon(points, colors)
func _append_arc(
points: PackedVector2Array,
center: Vector2,
start_angle: float,
end_angle: float,
) -> void:
var radius: float = minf(
float(profile.corner_radius),
minf(size.x, size.y) * 0.5,
)
for index: int in range(CORNER_SEGMENTS + 1):
var weight: float = float(index) / float(CORNER_SEGMENTS)
var angle: float = lerpf(start_angle, end_angle, weight)
points.append(center + Vector2(cos(angle), sin(angle)) * radius)
func _get_fill_color() -> Color:
if host_button.disabled:
return profile.disabled_fill
match host_button.get_draw_mode():
BaseButton.DRAW_PRESSED, BaseButton.DRAW_HOVER_PRESSED:
return profile.pressed_fill
BaseButton.DRAW_HOVER:
return profile.hover_fill
if host_button.has_focus():
return profile.hover_fill
return profile.normal_fill

View file

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

View file

@ -0,0 +1,97 @@
class_name BubbleHoverDrawer
extends Control
enum DrawerMode {
RIGHT,
LEFT_VERTICAL,
}
const DRAWER_COLOR: Color = Color(0.031, 0.122, 0.169, 1.0)
const TEXT_COLOR: Color = Color(0.764706, 0.87451, 0.901961, 1.0)
const RIGHT_SIZE: Vector2 = Vector2(132.0, 48.0)
const LEFT_VERTICAL_SIZE: Vector2 = Vector2(50.0, 176.0)
const OVERLAP: float = 12.0
var host_button: BaseButton
var drawer_mode: DrawerMode = DrawerMode.RIGHT
var _panel: Panel
var _label: Label
func configure(
button: BaseButton,
drawer_text: String,
mode: DrawerMode,
) -> void:
host_button = button
drawer_mode = mode
mouse_filter = Control.MOUSE_FILTER_IGNORE
show_behind_parent = true
_build_controls()
_label.text = drawer_text
_refresh_label_layout()
refresh_layout()
set_reveal(0.0)
func refresh_layout() -> void:
if host_button == null:
return
if drawer_mode == DrawerMode.RIGHT:
size = RIGHT_SIZE
position = Vector2(
host_button.size.x - OVERLAP,
(host_button.size.y - size.y) * 0.5,
)
pivot_offset = Vector2(0.0, size.y * 0.5)
else:
size = LEFT_VERTICAL_SIZE
position = Vector2(
-size.x + OVERLAP,
(host_button.size.y - size.y) * 0.5,
)
pivot_offset = Vector2(size.x, size.y * 0.5)
_refresh_label_layout()
func set_reveal(amount: float) -> void:
var reveal: float = clampf(amount, 0.0, 1.0)
visible = reveal > 0.001
scale = Vector2(maxf(reveal, 0.001), 1.0)
func _build_controls() -> void:
if _panel != null:
return
_panel = Panel.new()
_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
_panel.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
var panel_style := StyleBoxFlat.new()
panel_style.bg_color = DRAWER_COLOR
panel_style.corner_radius_top_left = 12
panel_style.corner_radius_top_right = 12
panel_style.corner_radius_bottom_right = 12
panel_style.corner_radius_bottom_left = 12
_panel.add_theme_stylebox_override("panel", panel_style)
add_child(_panel)
_label = Label.new()
_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
_label.add_theme_color_override("font_color", TEXT_COLOR)
_label.add_theme_font_size_override("font_size", 20)
_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
add_child(_label)
func _refresh_label_layout() -> void:
if _label == null:
return
if drawer_mode == DrawerMode.RIGHT:
_label.rotation = 0.0
_label.position = Vector2.ZERO
_label.size = size
else:
_label.rotation = -PI * 0.5
_label.position = Vector2(-OVERLAP * 0.5, size.y)
_label.size = Vector2(size.y, size.x)

View file

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

View file

@ -21,6 +21,10 @@ extends Resource
@export_range(0, 12, 1) var normal_border_width: int = 2
@export_range(0, 12, 1) var emphasized_border_width: int = 3
@export_group("Gradient")
@export var gradient_enabled: bool = false
@export_range(0.0, 0.5, 0.01) var gradient_top_lift: float = 0.12
@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)
@ -78,7 +82,11 @@ func _make_style(
style.content_margin_top = content_margin
style.content_margin_right = content_margin
style.content_margin_bottom = content_margin
style.bg_color = fill_color
style.bg_color = (
Color(fill_color.r, fill_color.g, fill_color.b, 0.0)
if gradient_enabled
else fill_color
)
style.border_width_left = outline_width
style.border_width_top = outline_width
style.border_width_right = outline_width

View file

@ -0,0 +1,22 @@
[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")
font_size_ratio = 0.14
hover_focus_scale = 1.02
hover_focus_lift = 0.0
emphasis_speed = 7.0
emphasized_deformation_scale = 0.0
contact_gap = 0.0
maximum_separation = 0.0
content_margin = 6.0
corner_radius = 36
normal_border_width = 0
emphasized_border_width = 0
gradient_enabled = false
normal_fill = Color(1, 1, 1, 1)
hover_fill = Color(1, 1, 1, 1)
pressed_fill = Color(1, 1, 1, 1)
disabled_fill = Color(1, 1, 1, 0.78)

View file

@ -0,0 +1,22 @@
[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")
font_size_ratio = 0.14
hover_focus_scale = 1.02
hover_focus_lift = 0.0
emphasis_speed = 7.0
emphasized_deformation_scale = 0.0
contact_gap = 0.0
maximum_separation = 0.0
content_margin = 6.0
corner_radius = 16
normal_border_width = 0
emphasized_border_width = 0
gradient_enabled = false
normal_fill = Color(1, 1, 1, 1)
hover_fill = Color(1, 1, 1, 1)
pressed_fill = Color(1, 1, 1, 1)
disabled_fill = Color(1, 1, 1, 0.78)

View file

@ -245,7 +245,7 @@ func _clear_directional_input_in_flight(generation: int) -> void:
func _is_directional_navigation(event: InputEvent) -> bool:
if event is InputEventKey and (event as InputEventKey).echo:
return false
# Tab is both Godot's default ui_focus_next key and NETfishing's default
# Tab is both Godot's default ui_focus_next key and straywild's default
# player-menu binding. The global focus presenter must leave that gameplay
# action alone; otherwise a remembered neutral focus seed consumes the key
# before PlayerMenu can open or close.

View file

@ -239,7 +239,7 @@ func _build_interface() -> void:
_instruction_label = Label.new()
_instruction_label.text = (
"auto-map walks through NETfishing's controller actions. "
"auto-map walks through straywild's controller actions. "
+ "select any row afterward to override it; the mapped back "
+ "control cancels capture."
)

BIN
ui/icons/new_menu/close.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bq8a7n8grdob3"
path.s3tc="res://.godot/imported/close.png-6d4985cc89fb9a07db5c6ce09f05566a.s3tc.ctex"
path.etc2="res://.godot/imported/close.png-6d4985cc89fb9a07db5c6ce09f05566a.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://ui/icons/new_menu/close.png"
dest_files=["res://.godot/imported/close.png-6d4985cc89fb9a07db5c6ce09f05566a.s3tc.ctex", "res://.godot/imported/close.png-6d4985cc89fb9a07db5c6ce09f05566a.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://br1f2ofv4575c"
path.s3tc="res://.godot/imported/credits.png-636657da1c8dc4ba52732813af1a703c.s3tc.ctex"
path.etc2="res://.godot/imported/credits.png-636657da1c8dc4ba52732813af1a703c.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://ui/icons/new_menu/credits.png"
dest_files=["res://.godot/imported/credits.png-636657da1c8dc4ba52732813af1a703c.s3tc.ctex", "res://.godot/imported/credits.png-636657da1c8dc4ba52732813af1a703c.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c8sty10uue6nm"
path.s3tc="res://.godot/imported/online.png-3f69c41cde4beb29d8f23e574a61641f.s3tc.ctex"
path.etc2="res://.godot/imported/online.png-3f69c41cde4beb29d8f23e574a61641f.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://ui/icons/new_menu/online.png"
dest_files=["res://.godot/imported/online.png-3f69c41cde4beb29d8f23e574a61641f.s3tc.ctex", "res://.godot/imported/online.png-3f69c41cde4beb29d8f23e574a61641f.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

BIN
ui/icons/new_menu/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -2,20 +2,22 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://ckqayis1ckb2v"
path="res://.godot/imported/netfishing_logo.png-82ab9fce5851dcc5239eca178e8cde99.ctex"
uid="uid://7qe6siny4ru7"
path.s3tc="res://.godot/imported/play.png-f35bbfc9549b806b0a83d0c3b4c7dc74.s3tc.ctex"
path.etc2="res://.godot/imported/play.png-f35bbfc9549b806b0a83d0c3b4c7dc74.etc2.ctex"
metadata={
"vram_texture": false
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://ui/assets/title/netfishing_logo.png"
dest_files=["res://.godot/imported/netfishing_logo.png-82ab9fce5851dcc5239eca178e8cde99.ctex"]
source_file="res://ui/icons/new_menu/play.png"
dest_files=["res://.godot/imported/play.png-f35bbfc9549b806b0a83d0c3b4c7dc74.s3tc.ctex", "res://.godot/imported/play.png-f35bbfc9549b806b0a83d0c3b4c7dc74.etc2.ctex"]
[params]
compress/mode=0
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
@ -37,4 +39,4 @@ process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cxqwvyg0qryg1"
path.s3tc="res://.godot/imported/settings.png-3654f1ee86a2237bbc3c9af7e818343c.s3tc.ctex"
path.etc2="res://.godot/imported/settings.png-3654f1ee86a2237bbc3c9af7e818343c.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://ui/icons/new_menu/settings.png"
dest_files=["res://.godot/imported/settings.png-3654f1ee86a2237bbc3c9af7e818343c.s3tc.ctex", "res://.godot/imported/settings.png-3654f1ee86a2237bbc3c9af7e818343c.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

View file

@ -439,7 +439,7 @@ func _choose_import_path() -> void:
_import_dialog.access = FileDialog.ACCESS_FILESYSTEM
_import_dialog.use_native_dialog = false
_import_dialog.filters = PackedStringArray([
"*.nfsave ; NETfishing progression archive",
"*.nfsave ; straywild progression archive",
])
_import_dialog.file_selected.connect(_import_file_selected)
if _interface_fonts != null:
@ -479,7 +479,7 @@ func _choose_export_path() -> void:
_export_dialog.access = FileDialog.ACCESS_FILESYSTEM
_export_dialog.use_native_dialog = false
_export_dialog.filters = PackedStringArray([
"*.nfsave ; NETfishing progression archive",
"*.nfsave ; straywild progression archive",
])
_export_dialog.file_selected.connect(_export_file_selected)
if _interface_fonts != null:
@ -716,7 +716,7 @@ func _safe_filename(value: String) -> String:
var result: String = value.strip_edges().to_lower().replace(" ", "-")
for character: String in ["/", "\\", ":", "*", "?", "\"", "<", ">", "|"]:
result = result.replace(character, "")
return "netfishing-save" if result.is_empty() else result
return "straywild-save" if result.is_empty() else result
func _on_slots_changed() -> void:

View file

@ -697,7 +697,7 @@ func _change_data_folder(path: String) -> void:
).to_lower()
_refresh_data_page()
return
_feedback.text = "data folder changed. NETfishing will close safely."
_feedback.text = "data folder changed. straywild will close safely."
_refresh_data_page()
get_tree().call_deferred("quit")
else:
@ -708,16 +708,16 @@ func _change_data_folder(path: String) -> void:
func _show_existing_data_folder_choice(path: String) -> void:
var dialog := ConfirmationDialog.new()
dialog.title = "existing NETfishing data"
dialog.title = "existing straywild data"
dialog.ok_button_text = "use selected data"
dialog.dialog_text = (
"the selected folder contains different NETfishing data.\n\n"
"the selected folder contains different straywild data.\n\n"
+ "choose one complete data set. data will not be merged."
)
dialog.add_button("replace selected data", false, "replace")
dialog.confirmed.connect(func() -> void:
if _data_root.use_existing_root(path):
_feedback.text = "data folder changed. NETfishing will close safely."
_feedback.text = "data folder changed. straywild will close safely."
get_tree().call_deferred("quit")
else:
_feedback.text = _data_root.error_message
@ -756,7 +756,7 @@ func _choose_identity_export(identity_type: String) -> void:
_export_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
_export_file_dialog.use_native_dialog = false
_export_file_dialog.filters = PackedStringArray([
"*.nfidentity ; NETfishing identity backup",
"*.nfidentity ; straywild identity backup",
])
_export_file_dialog.file_selected.connect(
_identity_export_file_selected
@ -786,7 +786,7 @@ func _choose_identity_import(identity_type: String) -> void:
_backup_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
_backup_file_dialog.use_native_dialog = false
_backup_file_dialog.filters = PackedStringArray([
"*.nfidentity ; NETfishing identity backup",
"*.nfidentity ; straywild identity backup",
])
_backup_file_dialog.file_selected.connect(
_identity_import_file_selected

View file

@ -45,7 +45,7 @@ horizontal_alignment = 1
layout_mode = 2
theme_override_colors/font_color = Color(0.624, 0.812, 0.824, 1)
theme_override_font_sizes/font_size = 18
text = "NETfishing is created by Woofmeow"
text = "straywild is created by Woofmeow"
horizontal_alignment = 1
[node name="Columns" type="HBoxContainer" parent="Paper/Margin/Layout"]

View file

@ -31,55 +31,18 @@ const SaveSlotsPageType = preload("res://ui/save_slots_page.gd")
const CurrencyPresentationType = preload(
"res://ui/currency_presentation.gd"
)
const DECORATIVE_FISH_TEXTURES: Array[Texture2D] = [
preload("res://fish/species/bass/fish_bass_striped.png"),
preload("res://fish/species/bluegill/fish_bluegill.png"),
preload("res://fish/species/carp/fish_carp_common.png"),
preload("res://fish/species/sunfish/fish_sunfish.png"),
]
const DECORATIVE_BUBBLE_TEXTURES: Array[Texture2D] = [
preload("res://ui/assets/title/bubbles/bubble1.png"),
preload("res://ui/assets/title/bubbles/bubble2.png"),
preload("res://ui/assets/title/bubbles/bubble3.png"),
]
const FIRST_FISH_DELAY_MIN: float = 2.0
const FIRST_FISH_DELAY_MAX: float = 5.0
const NEXT_FISH_DELAY_MIN: float = 5.0
const NEXT_FISH_DELAY_MAX: float = 12.0
const CROSSING_DURATION_MIN: float = 8.0
const CROSSING_DURATION_MAX: float = 16.0
const FISH_LONGEST_SIDE_MIN: float = 120.0
const FISH_LONGEST_SIDE_MAX: float = 210.0
const FISH_EDGE_MARGIN: float = 24.0
const BUBBLE_EVENT_DELAY_MIN: float = 4.0
const BUBBLE_EVENT_DELAY_MAX: float = 10.0
const BUBBLE_CLUSTER_DELAY_MIN: float = 0.25
const BUBBLE_CLUSTER_DELAY_MAX: float = 0.65
const BUBBLE_TRAVEL_DURATION_MIN: float = 7.0
const BUBBLE_TRAVEL_DURATION_MAX: float = 14.0
const BUBBLE_SCALE_MIN: float = 0.65
const BUBBLE_SCALE_MAX: float = 1.15
const BUBBLE_OPACITY_MIN: float = 0.55
const BUBBLE_OPACITY_MAX: float = 0.90
const BUBBLE_DRIFT_MIN: float = 10.0
const BUBBLE_DRIFT_MAX: float = 45.0
const BUBBLE_WOBBLE_MIN: float = 3.0
const BUBBLE_WOBBLE_MAX: float = 9.0
const BUBBLE_EDGE_MARGIN: float = 16.0
const MAX_DECORATIVE_BUBBLES: int = 6
const MAX_TRANSITION_BUBBLES: int = 30
const LOGO_ASPECT_RATIO: float = 2560.0 / 760.0
const LOGO_ASPECT_RATIO: float = 2560.0 / 480.0
const LOGO_WIDTH_FACTOR: float = 0.4784
const LOGO_MIN_WIDTH: float = 294.0
const LOGO_MAX_WIDTH: float = 662.0
const TITLE_HORIZONTAL_MARGIN: float = 48.0
const TITLE_DESKTOP_REFERENCE_SIZE: Vector2 = Vector2(1280.0, 720.0)
const TITLE_COMPACT_REFERENCE_SIZE: Vector2 = Vector2(640.0, 480.0)
const BUBBLE_FIELD_MAX_WIDTH: float = 440.0
const BUBBLE_FIELD_DESKTOP_HEIGHT: float = 318.0
const BUBBLE_FIELD_MAX_WIDTH: float = 500.0
const BUBBLE_FIELD_DESKTOP_HEIGHT: float = 360.0
const BUBBLE_FIELD_COMPACT_WIDTH: float = 294.0
const BUBBLE_FIELD_COMPACT_HEIGHT: float = 200.0
const BUBBLE_FIELD_DESKTOP_OFFSET_Y: float = -10.0
const BUBBLE_COMPACT_HEIGHT_THRESHOLD: float = 560.0
const START_PROMPT_MIN_SCALE: float = 0.985
const START_PROMPT_MAX_SCALE: float = 1.015
@ -87,6 +50,11 @@ const START_PROMPT_CYCLE_SECONDS: float = 3.0
const QUICK_MENU_ENTER_DURATION: float = 0.14
const QUICK_MENU_EXIT_DURATION: float = 0.10
const QUICK_MENU_ENTER_SCALE: float = 0.97
const INTRO_PROMPT_FADE_DURATION: float = 0.24
const INTRO_LOGO_FLOAT_DURATION: float = 0.72
const INTRO_MENU_REVEAL_DELAY: float = 0.34
const INTRO_MENU_REVEAL_DURATION: float = 0.30
const INTRO_LOGO_CENTER_Y_RATIO: float = 0.43
signal new_game_requested(world_layout: StringName, world_seed: int)
signal continue_game_requested
@ -113,6 +81,7 @@ enum ConfirmationAction {
@onready var _quit_button: BubbleButtonType = %QuitButton
@onready var _join_game_button: BubbleButtonType = %JoinGameButton
@onready var _feedback_label: RichTextLabel = %FeedbackLabel
@onready var _continue_stats_drawer: RichTextLabel = %ContinueStatsDrawer
@onready var _confirmation_page: TitleConfirmationBubblePageType = (
%ConfirmationPage
)
@ -121,11 +90,6 @@ enum ConfirmationAction {
%TitlePresentationScaleRoot
)
@onready var _background: ColorRect = %Background
@onready var _decorative_fish_layer: Control = %DecorativeFishLayer
@onready var _decorative_fish_timer: Timer = %DecorativeFishTimer
@onready var _decorative_bubble_layer: Control = %DecorativeBubbleLayer
@onready var _decorative_bubble_event_timer: Timer = %DecorativeBubbleEventTimer
@onready var _decorative_bubble_cluster_timer: Timer = %DecorativeBubbleClusterTimer
@onready var _title_logo: TextureRect = %TitleLogo
@onready var _playtest_label: Label = %PlaytestLabel
@onready var _presentation_center: CenterContainer = %Center
@ -149,17 +113,6 @@ var _settings_manager: SettingsManagerType
var _inspection: SaveInspectionType
var _confirmation_action: ConfirmationAction = ConfirmationAction.NONE
var _action_in_progress: bool = false
var _decorative_rng := RandomNumberGenerator.new()
var _decorative_fish: TextureRect
var _decorative_fish_tween: Tween
var _decorative_presentation_ready: bool = false
var _decorative_presentation_active: bool = false
var _decorative_generation: int = 0
var _decorative_bubbles: Array[TextureRect] = []
var _decorative_bubble_tweens: Dictionary[int, Tween] = {}
var _transition_bubble_ids: Dictionary[int, bool] = {}
var _pending_cluster_bubbles: int = 0
var _minimal_presentation: bool = false
var _awaiting_start_input: bool = false
var _start_prompt_elapsed: float = 0.0
var _navigation_focus_active: bool = false
@ -219,27 +172,16 @@ func _ready() -> void:
_settings_panel.closed.connect(_on_settings_closed)
_settings_panel.closing.connect(_on_settings_closing)
_settings_panel.opened.connect(_on_settings_opened)
_settings_panel.navigation_transition_started.connect(
_emit_navigation_bubble_flurry
)
_credits_page.back_requested.connect(_close_credits)
_save_slots_page.back_requested.connect(_close_save_slots)
_save_slots_page.play_requested.connect(_on_slot_play_requested)
_save_slots_page.create_requested.connect(_on_new_slot_requested)
_bubble_field.configure(_get_title_buttons())
_bubble_field.motion_scale = 0.0
_decorative_fish_timer.timeout.connect(_on_decorative_fish_timer_timeout)
_decorative_bubble_event_timer.timeout.connect(
_on_decorative_bubble_event_timer_timeout
)
_decorative_bubble_cluster_timer.timeout.connect(
_on_decorative_bubble_cluster_timer_timeout
)
_bubble_field.motion_scale = 1.0
visibility_changed.connect(_on_title_visibility_changed)
resized.connect(_update_responsive_title_stage)
get_window().size_changed.connect(_update_responsive_title_stage)
_start_prompt_label.resized.connect(_update_start_prompt_pivot)
_decorative_rng.randomize()
set_process(false)
call_deferred("_update_responsive_title_stage")
call_deferred("_update_start_prompt_pivot")
@ -269,19 +211,9 @@ func setup(
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
_refresh_save_inspection()
show()
_decorative_presentation_ready = true
_start_decorative_presentation()
_begin_title_entry()
func set_minimal_presentation(enabled: bool) -> void:
_minimal_presentation = enabled
if _minimal_presentation:
_stop_decorative_presentation()
elif visible:
_start_decorative_presentation()
func reopen() -> void:
_cancel_title_entry_transition()
_cancel_title_settings_transition()
@ -295,7 +227,6 @@ func reopen() -> void:
_save_slots_page.close_page()
_refresh_save_inspection()
show()
_start_decorative_presentation()
_start_entry_prompt_animation()
@ -315,6 +246,8 @@ func reopen_to_menu() -> void:
func open_join_game_page(endpoint: String = "") -> void:
_cancel_title_entry_transition()
_awaiting_start_input = false
_set_title_bubbles_interactive(false)
_release_primary_menu_focus()
_start_prompt_center.hide()
_presentation_center.hide()
_settings_panel.hide()
@ -353,6 +286,7 @@ func _close_join_game() -> void:
_presentation_center.show()
_button_center.show()
_start_prompt_center.hide()
_set_title_bubbles_interactive(true)
_focus_initial_button()
@ -395,28 +329,6 @@ func is_awaiting_start_input() -> bool:
return _awaiting_start_input
func is_decorative_presentation_active() -> bool:
return _decorative_presentation_active
func get_active_decorative_fish_count() -> int:
return 1 if is_instance_valid(_decorative_fish) else 0
func get_active_decorative_bubble_count() -> int:
return _decorative_bubbles.size()
func is_decorative_bubble_scheduler_active() -> bool:
return (
_decorative_presentation_active
and (
not _decorative_bubble_event_timer.is_stopped()
or not _decorative_bubble_cluster_timer.is_stopped()
)
)
func _update_title_layout() -> void:
if not is_node_ready():
return
@ -464,6 +376,12 @@ func _update_title_layout() -> void:
Vector2(field_width, field_height),
compact_layout
)
_bubble_field.position = Vector2(
0.0,
BUBBLE_FIELD_DESKTOP_OFFSET_Y
* field_height
/ BUBBLE_FIELD_DESKTOP_HEIGHT,
)
_configure_title_controller_navigation()
if not _title_settings_transition_active:
call_deferred("_capture_title_bubble_rest_position")
@ -520,12 +438,6 @@ func _update_world_preview_resolution() -> void:
_world_pixel_size,
displayed_size
)
var shader_material := _background.material as ShaderMaterial
if shader_material != null:
shader_material.set_shader_parameter(
"virtual_pixel_density",
Vector2(grid_size)
)
var logo_material := _title_logo.material as ShaderMaterial
if logo_material != null:
var render_scale: float = (
@ -542,23 +454,6 @@ func _update_world_preview_resolution() -> void:
)
func _snap_world_preview(value: Vector2) -> Vector2:
var displayed_size := Vector2i(
maxi(1, roundi(size.x)),
maxi(1, roundi(size.y))
)
var grid_size: Vector2i = PlayerSettings.get_world_grid_size(
_world_pixel_size,
displayed_size
)
var render_scale: float = float(grid_size.y) / float(displayed_size.y)
var step: float = 1.0 / render_scale
return Vector2(
roundf(value.x / step) * step,
roundf(value.y / step) * step
)
func _process(delta: float) -> void:
if not visible:
return
@ -725,6 +620,9 @@ func _prepare_awaiting_start_input() -> void:
_continue_stats_hovered = false
_continue_stats_focused = false
_cancel_continue_stats_fade()
_continue_stats_drawer.hide()
_continue_stats_drawer.modulate.a = 0.0
_continue_stats_drawer.scale = Vector2(1.0, 0.001)
_main_content.show()
_start_prompt_center.show()
_start_prompt_center.modulate.a = 1.0
@ -775,7 +673,6 @@ func _reveal_primary_menu() -> void:
_navigation_focus_active = false
_release_title_focus()
_bubble_motion_root.position = Vector2.ZERO
_branding_motion_root.position = Vector2.ZERO
_bubble_field.pivot_offset = _bubble_field.size * 0.5
_bubble_field.scale = Vector2.ONE * QUICK_MENU_ENTER_SCALE
_bubble_field.modulate.a = 0.0
@ -784,20 +681,28 @@ func _reveal_primary_menu() -> void:
_start_prompt_center,
"modulate:a",
0.0,
QUICK_MENU_EXIT_DURATION
INTRO_PROMPT_FADE_DURATION
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
_title_entry_transition.tween_property(
_branding_motion_root,
"position",
Vector2.ZERO,
INTRO_LOGO_FLOAT_DURATION
).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT)
_title_entry_transition.tween_property(
_bubble_field,
"scale",
Vector2.ONE,
QUICK_MENU_ENTER_DURATION
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
INTRO_MENU_REVEAL_DURATION
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT).set_delay(
INTRO_MENU_REVEAL_DELAY
)
_title_entry_transition.tween_property(
_bubble_field,
"modulate:a",
1.0,
QUICK_MENU_ENTER_DURATION
)
INTRO_MENU_REVEAL_DURATION
).set_delay(INTRO_MENU_REVEAL_DELAY)
_title_entry_transition.finished.connect(
_finish_primary_menu_reveal.bind(generation),
CONNECT_ONE_SHOT
@ -814,6 +719,7 @@ func _finish_primary_menu_reveal(generation: int) -> void:
_title_entry_transition_active = false
_start_prompt_center.hide()
_start_prompt_center.modulate.a = 1.0
_branding_motion_root.position = Vector2.ZERO
_bubble_field.scale = Vector2.ONE
_bubble_field.modulate.a = 1.0
_feedback_label.modulate.a = 0.0
@ -855,10 +761,18 @@ func _prepare_intro_presentation(generation: int) -> void:
return
_branding_motion_root.size = _branding_slot.size
_bubble_motion_root.size = _bubble_layout_slot.size
_branding_motion_root.position = Vector2.ZERO
_bubble_motion_root.position = Vector2.ZERO
_title_content_rest_position = _presentation_center.position
_title_bubble_rest_position = _bubble_field.position
var branding_rect: Rect2 = _get_title_stage_rect(_branding_slot)
var desired_branding_center_y: float = (
_title_presentation_scale_root.size.y
* INTRO_LOGO_CENTER_Y_RATIO
)
_branding_motion_root.position = Vector2(
0.0,
desired_branding_center_y - branding_rect.get_center().y,
)
_intro_geometry_ready = true
@ -908,7 +822,7 @@ func _update_continue_stats_visibility() -> void:
and _presentation_center.visible
)
if requested_visible:
_feedback_label.text = _get_continue_stats_text()
_continue_stats_drawer.text = _get_continue_stats_text()
_fade_continue_stats_to(1.0 if requested_visible else 0.0)
@ -922,16 +836,47 @@ func _fade_continue_stats_to(target_opacity: float) -> void:
if not is_node_ready():
return
_cancel_continue_stats_fade()
if is_equal_approx(_feedback_label.modulate.a, target_opacity):
_feedback_label.modulate.a = target_opacity
var target_scale := Vector2(
1.0,
1.0 if target_opacity > 0.0 else 0.001,
)
if target_opacity > 0.0:
_continue_stats_drawer.show()
if (
is_equal_approx(
_continue_stats_drawer.modulate.a,
target_opacity,
)
and _continue_stats_drawer.scale.is_equal_approx(target_scale)
):
_continue_stats_drawer.modulate.a = target_opacity
_continue_stats_drawer.scale = target_scale
if target_opacity <= 0.0:
_continue_stats_drawer.hide()
return
_continue_stats_fade = create_tween()
_continue_stats_fade = create_tween().set_parallel(true)
_continue_stats_fade.tween_property(
_feedback_label,
_continue_stats_drawer,
"modulate:a",
target_opacity,
QUICK_MENU_ENTER_DURATION
).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT)
_continue_stats_fade.tween_property(
_continue_stats_drawer,
"scale",
target_scale,
QUICK_MENU_ENTER_DURATION
).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT)
if target_opacity <= 0.0:
_continue_stats_fade.chain().tween_callback(
_finish_continue_stats_hide
)
func _finish_continue_stats_hide() -> void:
_continue_stats_fade = null
if _continue_stats_drawer.modulate.a <= 0.001:
_continue_stats_drawer.hide()
func _cancel_continue_stats_fade() -> void:
@ -1180,7 +1125,6 @@ func _begin_confirmation_open() -> void:
_confirmation_transition_generation += 1
_cancel_confirmation_transition()
_confirmation_transition_active = true
_emit_navigation_bubble_flurry()
_confirmation_title_content_rest_position = _presentation_center.position
_set_confirmation_stage_rect()
_confirmation_page.hide_page()
@ -1238,7 +1182,6 @@ func _begin_confirmation_return() -> void:
_confirmation_transition_generation += 1
_cancel_confirmation_transition()
_confirmation_transition_active = true
_emit_navigation_bubble_flurry()
_confirmation_page.lock_interaction()
var generation: int = _confirmation_transition_generation
_confirmation_page.transition_out(
@ -1375,7 +1318,6 @@ func _begin_title_cluster_exit() -> void:
_title_settings_transition_generation += 1
_cancel_title_settings_tween()
_title_settings_transition_active = true
_emit_navigation_bubble_flurry()
_title_content_rest_position = _presentation_center.position
_title_bubble_rest_position = _bubble_field.position
_set_title_bubbles_interactive(false)
@ -1475,6 +1417,8 @@ func _finish_title_cluster_return(generation: int) -> void:
func _set_title_bubbles_interactive(interactive: bool) -> void:
for bubble: BubbleButton in _get_title_buttons():
if not interactive:
bubble.reset_hover_presentation()
bubble.focus_mode = (
Control.FOCUS_ALL if interactive else Control.FOCUS_NONE
)
@ -1491,21 +1435,39 @@ func _configure_title_controller_navigation() -> void:
ControllerFocusNavigationType.configure_spatial_neighbors(
_get_title_buttons()
)
# The large Play bubble overlaps the inward edge of every surrounding
# bubble. Center-only spatial scoring otherwise links the four outside
# bubbles into a ring with no route back into Play.
_join_game_button.focus_neighbor_right = (
_play_button.focus_neighbor_right = (
_play_button.get_path_to(_join_game_button)
)
_join_game_button.focus_neighbor_left = (
_join_game_button.get_path_to(_play_button)
)
_join_game_button.focus_neighbor_bottom = (
_join_game_button.get_path_to(_settings_button)
)
_settings_button.focus_neighbor_left = (
_settings_button.get_path_to(_play_button)
)
_credits_button.focus_neighbor_right = (
_settings_button.focus_neighbor_top = (
_settings_button.get_path_to(_join_game_button)
)
_settings_button.focus_neighbor_bottom = (
_settings_button.get_path_to(_credits_button)
)
_credits_button.focus_neighbor_left = (
_credits_button.get_path_to(_play_button)
)
_credits_button.focus_neighbor_top = (
_credits_button.get_path_to(_settings_button)
)
_credits_button.focus_neighbor_bottom = (
_credits_button.get_path_to(_quit_button)
)
_quit_button.focus_neighbor_left = (
_quit_button.get_path_to(_play_button)
)
_quit_button.focus_neighbor_top = (
_quit_button.get_path_to(_credits_button)
)
func _capture_title_bubble_rest_position() -> void:
@ -1575,7 +1537,7 @@ func _refresh_save_inspection() -> void:
_delete_button.disabled = not _inspection.can_delete()
_feedback_label.text = _center_feedback_text(_inspection.message)
if _inspection.status == SaveInspectionType.Status.VALID_SUPPORTED:
_feedback_label.text = _get_continue_stats_text()
_continue_stats_drawer.text = _get_continue_stats_text()
if not _inspection.can_continue():
_hide_continue_stats_context()
else:
@ -1609,11 +1571,8 @@ func _on_quit_pressed() -> void:
func _on_title_visibility_changed() -> void:
if not _decorative_presentation_ready:
return
if visible:
set_process(true)
_start_decorative_presentation()
else:
_hide_continue_stats_context()
_stop_entry_prompt_animation()
@ -1621,482 +1580,3 @@ func _on_title_visibility_changed() -> void:
_modal_restore_navigation_focus = false
_reset_confirmation()
_credits_page.close_page()
_stop_decorative_presentation()
func _start_decorative_presentation() -> void:
if (
not _decorative_presentation_ready
or not visible
or _decorative_presentation_active
or _minimal_presentation
):
return
_decorative_generation += 1
_decorative_presentation_active = true
_schedule_next_decorative_fish(
FIRST_FISH_DELAY_MIN,
FIRST_FISH_DELAY_MAX
)
_schedule_next_decorative_bubble_event(
BUBBLE_EVENT_DELAY_MIN,
BUBBLE_EVENT_DELAY_MAX
)
func _stop_decorative_presentation() -> void:
_decorative_generation += 1
_decorative_presentation_active = false
_decorative_fish_timer.stop()
if _decorative_fish_tween != null:
_decorative_fish_tween.kill()
_decorative_fish_tween = null
if is_instance_valid(_decorative_fish):
_decorative_fish.queue_free()
_decorative_fish = null
_decorative_bubble_event_timer.stop()
_decorative_bubble_cluster_timer.stop()
_pending_cluster_bubbles = 0
for bubble_tween: Tween in _decorative_bubble_tweens.values():
if bubble_tween != null:
bubble_tween.kill()
_decorative_bubble_tweens.clear()
for bubble: TextureRect in _decorative_bubbles:
if is_instance_valid(bubble):
bubble.queue_free()
_decorative_bubbles.clear()
_transition_bubble_ids.clear()
func _schedule_next_decorative_fish(
minimum_delay: float,
maximum_delay: float,
) -> void:
if not _decorative_presentation_active or not visible:
return
_decorative_fish_timer.start(
_decorative_rng.randf_range(minimum_delay, maximum_delay)
)
func _on_decorative_fish_timer_timeout() -> void:
if (
not _decorative_presentation_active
or not visible
or is_instance_valid(_decorative_fish)
):
return
_spawn_decorative_fish(_decorative_generation)
func _spawn_decorative_fish(generation: int) -> void:
if (
generation != _decorative_generation
or not _decorative_presentation_active
or not visible
or DECORATIVE_FISH_TEXTURES.is_empty()
):
return
var layer_size: Vector2 = _decorative_fish_layer.size
if layer_size.x <= 1.0 or layer_size.y <= 1.0:
_schedule_next_decorative_fish(0.5, 1.0)
return
var texture: Texture2D = DECORATIVE_FISH_TEXTURES[
_decorative_rng.randi_range(0, DECORATIVE_FISH_TEXTURES.size() - 1)
]
var source_size: Vector2 = texture.get_size()
if source_size.x <= 0.0 or source_size.y <= 0.0:
_schedule_next_decorative_fish(
NEXT_FISH_DELAY_MIN,
NEXT_FISH_DELAY_MAX
)
return
var longest_side: float = _decorative_rng.randf_range(
FISH_LONGEST_SIDE_MIN,
FISH_LONGEST_SIDE_MAX
)
var presentation_scale: float = longest_side / maxf(
source_size.x,
source_size.y
)
var presentation_size: Vector2 = source_size * presentation_scale
var fish_control := TextureRect.new()
fish_control.name = "DecorativeFish"
fish_control.texture = texture
fish_control.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
fish_control.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
fish_control.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
fish_control.mouse_filter = Control.MOUSE_FILTER_IGNORE
fish_control.custom_minimum_size = presentation_size
fish_control.size = presentation_size
fish_control.modulate.a = 0.46
var direction: float = (
1.0
if _decorative_rng.randi_range(0, 1) == 1
else -1.0
)
fish_control.flip_h = direction > 0.0
var minimum_y: float = maxf(24.0, layer_size.y * 0.08)
var maximum_y: float = maxf(
minimum_y,
layer_size.y - presentation_size.y - maxf(24.0, layer_size.y * 0.08)
)
var base_y: float = _decorative_rng.randf_range(minimum_y, maximum_y)
var bob_amplitude: float = _decorative_rng.randf_range(3.0, 8.0)
var bob_speed: float = _decorative_rng.randf_range(0.65, 1.15)
var crossing_duration: float = _decorative_rng.randf_range(
CROSSING_DURATION_MIN,
CROSSING_DURATION_MAX
)
var horizontal_bounds: Vector2 = get_decorative_fish_crossing_bounds(
layer_size.x,
presentation_size.x,
direction,
)
fish_control.position = Vector2(horizontal_bounds.x, base_y)
_decorative_fish_layer.add_child(fish_control)
_decorative_fish = fish_control
_decorative_fish_tween = create_tween()
_decorative_fish_tween.tween_method(
_update_decorative_fish.bind(
fish_control,
direction,
base_y,
bob_amplitude,
bob_speed,
crossing_duration,
generation
),
0.0,
1.0,
crossing_duration
)
_decorative_fish_tween.finished.connect(
_on_decorative_fish_finished.bind(fish_control, generation),
CONNECT_ONE_SHOT
)
func _update_decorative_fish(
progress: float,
fish_control: TextureRect,
direction: float,
base_y: float,
bob_amplitude: float,
bob_speed: float,
crossing_duration: float,
generation: int,
) -> void:
if (
generation != _decorative_generation
or not _decorative_presentation_active
or not is_instance_valid(fish_control)
):
return
var horizontal_bounds: Vector2 = get_decorative_fish_crossing_bounds(
_decorative_fish_layer.size.x,
fish_control.size.x,
direction,
)
fish_control.position.x = lerpf(
horizontal_bounds.x, horizontal_bounds.y, progress
)
var bobbed_y: float = (
base_y
+ sin(progress * crossing_duration * bob_speed) * bob_amplitude
)
fish_control.position.y = clampf(
bobbed_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)
static func get_decorative_fish_crossing_bounds(
layer_width: float,
fish_width: float,
direction: float,
) -> Vector2:
var outside_left: float = -fish_width - FISH_EDGE_MARGIN
var outside_right: float = layer_width + FISH_EDGE_MARGIN
return (
Vector2(outside_left, outside_right)
if direction > 0.0
else Vector2(outside_right, outside_left)
)
func _on_decorative_fish_finished(
fish_control: TextureRect,
generation: int,
) -> void:
if generation != _decorative_generation:
return
_decorative_fish_tween = null
if is_instance_valid(fish_control):
fish_control.queue_free()
if _decorative_fish == fish_control:
_decorative_fish = null
_schedule_next_decorative_fish(
NEXT_FISH_DELAY_MIN,
NEXT_FISH_DELAY_MAX
)
func _schedule_next_decorative_bubble_event(
minimum_delay: float,
maximum_delay: float,
) -> void:
if not _decorative_presentation_active or not visible:
return
_decorative_bubble_event_timer.start(
_decorative_rng.randf_range(minimum_delay, maximum_delay)
)
func _on_decorative_bubble_event_timer_timeout() -> void:
if not _decorative_presentation_active or not visible:
return
var available_slots: int = (
MAX_DECORATIVE_BUBBLES - _get_idle_decorative_bubble_count()
)
if available_slots <= 0:
_schedule_next_decorative_bubble_event(
BUBBLE_EVENT_DELAY_MIN,
BUBBLE_EVENT_DELAY_MAX
)
return
var event_size: int = _choose_decorative_bubble_event_size()
event_size = mini(event_size, available_slots)
if not _spawn_decorative_bubble(_decorative_generation):
_schedule_next_decorative_bubble_event(0.5, 1.0)
return
_pending_cluster_bubbles = event_size - 1
if _pending_cluster_bubbles > 0:
_start_decorative_bubble_cluster_timer()
else:
_schedule_next_decorative_bubble_event(
BUBBLE_EVENT_DELAY_MIN,
BUBBLE_EVENT_DELAY_MAX
)
func _choose_decorative_bubble_event_size() -> int:
var roll: float = _decorative_rng.randf()
if roll < 0.75:
return 1
if roll < 0.95:
return 2
return 3
func _start_decorative_bubble_cluster_timer() -> void:
_decorative_bubble_cluster_timer.start(
_decorative_rng.randf_range(
BUBBLE_CLUSTER_DELAY_MIN,
BUBBLE_CLUSTER_DELAY_MAX
)
)
func _on_decorative_bubble_cluster_timer_timeout() -> void:
if (
not _decorative_presentation_active
or not visible
or _pending_cluster_bubbles <= 0
):
_pending_cluster_bubbles = 0
return
if _get_idle_decorative_bubble_count() < MAX_DECORATIVE_BUBBLES:
_spawn_decorative_bubble(_decorative_generation)
_pending_cluster_bubbles -= 1
if _pending_cluster_bubbles > 0:
_start_decorative_bubble_cluster_timer()
else:
_schedule_next_decorative_bubble_event(
BUBBLE_EVENT_DELAY_MIN,
BUBBLE_EVENT_DELAY_MAX
)
func _spawn_decorative_bubble(
generation: int,
start_y_ratio: float = -1.0,
normalized_x_override: float = -1.0,
is_transition_bubble: bool = false,
) -> bool:
if (
generation != _decorative_generation
or not _decorative_presentation_active
or not visible
or DECORATIVE_BUBBLE_TEXTURES.is_empty()
):
return false
if is_transition_bubble:
if _transition_bubble_ids.size() >= MAX_TRANSITION_BUBBLES:
return false
elif _get_idle_decorative_bubble_count() >= MAX_DECORATIVE_BUBBLES:
return false
var layer_size: Vector2 = _decorative_bubble_layer.size
if layer_size.x <= 1.0 or layer_size.y <= 1.0:
return false
var texture: Texture2D = DECORATIVE_BUBBLE_TEXTURES[
_decorative_rng.randi_range(
0,
DECORATIVE_BUBBLE_TEXTURES.size() - 1
)
]
var presentation_scale: float = _decorative_rng.randf_range(
BUBBLE_SCALE_MIN,
BUBBLE_SCALE_MAX
)
var presentation_size: Vector2 = texture.get_size() * presentation_scale
var bubble := TextureRect.new()
bubble.name = "DecorativeBubble"
bubble.texture = texture
bubble.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
bubble.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
bubble.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE
bubble.size = presentation_size
bubble.modulate.a = _decorative_rng.randf_range(
BUBBLE_OPACITY_MIN,
BUBBLE_OPACITY_MAX
)
_decorative_bubble_layer.add_child(bubble)
_decorative_bubbles.append(bubble)
if is_transition_bubble:
_transition_bubble_ids[bubble.get_instance_id()] = true
var normalized_x: float = normalized_x_override
if normalized_x < 0.0:
normalized_x = _decorative_rng.randf_range(0.08, 0.92)
var drift_direction: float = (
1.0 if _decorative_rng.randi_range(0, 1) == 1 else -1.0
)
var horizontal_drift: float = (
_decorative_rng.randf_range(
BUBBLE_DRIFT_MIN,
BUBBLE_DRIFT_MAX
)
* drift_direction
)
var wobble_amplitude: float = _decorative_rng.randf_range(
BUBBLE_WOBBLE_MIN,
BUBBLE_WOBBLE_MAX
)
var wobble_cycles: float = _decorative_rng.randf_range(0.8, 1.6)
var wobble_phase: float = _decorative_rng.randf_range(0.0, TAU)
var travel_duration: float = _decorative_rng.randf_range(
BUBBLE_TRAVEL_DURATION_MIN,
BUBBLE_TRAVEL_DURATION_MAX
)
if start_y_ratio >= 0.0:
var full_start_y: float = (
layer_size.y + presentation_size.y + BUBBLE_EDGE_MARGIN
)
var end_y: float = -presentation_size.y - BUBBLE_EDGE_MARGIN
var burst_start_y: float = (
start_y_ratio * layer_size.y - presentation_size.y * 0.5
)
var remaining_distance: float = maxf(burst_start_y - end_y, 1.0)
var full_distance: float = maxf(full_start_y - end_y, 1.0)
travel_duration *= remaining_distance / full_distance
_update_decorative_bubble(
0.0,
bubble,
normalized_x,
horizontal_drift,
wobble_amplitude,
wobble_cycles,
wobble_phase,
start_y_ratio,
generation
)
var bubble_tween: Tween = create_tween()
_decorative_bubble_tweens[bubble.get_instance_id()] = bubble_tween
bubble_tween.tween_method(
_update_decorative_bubble.bind(
bubble,
normalized_x,
horizontal_drift,
wobble_amplitude,
wobble_cycles,
wobble_phase,
start_y_ratio,
generation
),
0.0,
1.0,
travel_duration
)
bubble_tween.finished.connect(
_on_decorative_bubble_finished.bind(bubble, generation),
CONNECT_ONE_SHOT
)
return true
func _update_decorative_bubble(
progress: float,
bubble: TextureRect,
normalized_x: float,
horizontal_drift: float,
wobble_amplitude: float,
wobble_cycles: float,
wobble_phase: float,
start_y_ratio: float,
generation: int,
) -> void:
if (
generation != _decorative_generation
or not _decorative_presentation_active
or not is_instance_valid(bubble)
):
return
var layer_size: Vector2 = _decorative_bubble_layer.size
var start_y: float = (
layer_size.y + bubble.size.y + BUBBLE_EDGE_MARGIN
)
if start_y_ratio >= 0.0:
start_y = start_y_ratio * layer_size.y - bubble.size.y * 0.5
var end_y: float = -bubble.size.y - BUBBLE_EDGE_MARGIN
var base_x: float = normalized_x * layer_size.x
var wobble: float = sin(
wobble_phase + progress * TAU * wobble_cycles
) * wobble_amplitude
bubble.position = _snap_world_preview(Vector2(
base_x
+ horizontal_drift * progress
+ wobble
- bubble.size.x * 0.5,
lerpf(start_y, end_y, progress)
))
func _on_decorative_bubble_finished(
bubble: TextureRect,
generation: int,
) -> void:
if generation != _decorative_generation:
return
if is_instance_valid(bubble):
_decorative_bubble_tweens.erase(bubble.get_instance_id())
_transition_bubble_ids.erase(bubble.get_instance_id())
_decorative_bubbles.erase(bubble)
bubble.queue_free()
func _get_idle_decorative_bubble_count() -> int:
return _decorative_bubbles.size() - _transition_bubble_ids.size()
func _emit_navigation_bubble_flurry() -> void:
# Page changes now use a quick fade and scale without travel bubbles.
pass
func _exit_tree() -> void:
_stop_decorative_presentation()

View file

@ -1,29 +1,36 @@
[gd_scene load_steps=25 format=3]
[gd_scene load_steps=28 format=3]
[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="PackedScene" path="res://ui/settings_panel.tscn" id="3_settings"]
[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="Shader" path="res://ui/player_menu_pattern.gdshader" id="4_grass_shader"]
[ext_resource type="Texture2D" path="res://ui/assets/title/straywild_logo.png" id="5_logo"]
[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"]
[ext_resource type="PackedScene" path="res://ui/title_confirmation_bubble_page.tscn" id="10_confirmation_page"]
[ext_resource type="PackedScene" path="res://ui/network/join_game_page.tscn" id="11_join_page"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/online_dark.png" id="12_online_dark"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/settings_dark.png" id="13_settings_dark"]
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/x_dark.png" id="14_x_dark"]
[ext_resource type="Texture2D" path="res://ui/icons/new_menu/online.png" id="12_online_dark"]
[ext_resource type="Texture2D" path="res://ui/icons/new_menu/settings.png" id="13_settings_dark"]
[ext_resource type="Texture2D" path="res://ui/icons/new_menu/close.png" id="14_x_dark"]
[ext_resource type="PackedScene" path="res://ui/title_credits_page.tscn" id="15_credits_page"]
[ext_resource type="Texture2D" path="res://ui/icons/main_menu/continue.png" id="16_continue"]
[ext_resource type="Texture2D" path="res://ui/icons/new_menu/play.png" id="16_continue"]
[ext_resource type="Texture2D" path="res://ui/icons/main_menu/new_game.png" id="17_new_game"]
[ext_resource type="Texture2D" path="res://ui/icons/main_menu/credits.png" id="18_credits"]
[ext_resource type="Texture2D" path="res://ui/icons/new_menu/credits.png" id="18_credits"]
[ext_resource type="Texture2D" path="res://ui/icons/main_menu/delete_save.png" id="19_delete_save"]
[ext_resource type="PackedScene" path="res://ui/new_game_setup_page.tscn" id="20_new_game_setup"]
[ext_resource type="PackedScene" path="res://ui/save_slots_page.tscn" id="21_save_slots"]
[ext_resource type="Texture2D" path="res://world/generation/chunks/assets/chunk_0000_grass_lite.png" id="22_grass_pattern"]
[ext_resource type="Resource" path="res://ui/components/bubble_menu/title_menu_profile.tres" id="23_title_menu_profile"]
[ext_resource type="Resource" path="res://ui/components/bubble_menu/title_secondary_menu_profile.tres" id="24_title_secondary_menu_profile"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water"]
shader = ExtResource("4_water_shader")
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_grass"]
shader = ExtResource("4_grass_shader")
shader_parameter/pattern_texture = ExtResource("22_grass_pattern")
shader_parameter/source_tile_size = Vector2(128, 128)
shader_parameter/display_scale = 1.0
shader_parameter/scroll_velocity_pixels = Vector2(-7.5, -7.5)
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_logo"]
shader = ExtResource("6_logo_shader")
@ -39,6 +46,17 @@ corner_radius_top_right = 14
corner_radius_bottom_right = 14
corner_radius_bottom_left = 14
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_continue_stats_drawer"]
content_margin_left = 10.0
content_margin_top = 18.0
content_margin_right = 10.0
content_margin_bottom = 2.0
bg_color = Color(0.031, 0.122, 0.169, 1)
corner_radius_top_left = 14
corner_radius_top_right = 14
corner_radius_bottom_right = 14
corner_radius_bottom_left = 14
[node name="TitleScreen" type="Control"]
unique_name_in_owner = true
z_index = 200
@ -55,7 +73,7 @@ script = ExtResource("1_script")
[node name="Background" type="ColorRect" parent="."]
unique_name_in_owner = true
visible = false
material = SubResource("ShaderMaterial_title_water")
material = SubResource("ShaderMaterial_title_grass")
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
@ -64,42 +82,6 @@ grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="DecorativeFishLayer" type="Control" parent="."]
unique_name_in_owner = true
z_index = 1
clip_contents = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="DecorativeFishTimer" type="Timer" parent="."]
unique_name_in_owner = true
one_shot = true
[node name="DecorativeBubbleLayer" type="Control" parent="."]
unique_name_in_owner = true
z_index = 2
clip_contents = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="DecorativeBubbleEventTimer" type="Timer" parent="."]
unique_name_in_owner = true
one_shot = true
[node name="DecorativeBubbleClusterTimer" type="Timer" parent="."]
unique_name_in_owner = true
one_shot = true
[node name="ResponsiveTitleStage" type="Control" parent="."]
unique_name_in_owner = true
z_index = 10
@ -122,13 +104,13 @@ mouse_filter = 1
unique_name_in_owner = true
z_index = 12
layout_mode = 1
anchor_top = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = -104.0
offset_bottom = -48.0
anchor_bottom = 0.5
offset_top = 45.0
offset_bottom = 101.0
grow_horizontal = 2
grow_vertical = 0
grow_vertical = 2
mouse_filter = 2
[node name="StartPromptLabel" type="Label" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/StartPromptCenter"]
@ -136,8 +118,6 @@ unique_name_in_owner = true
layout_mode = 2
mouse_filter = 2
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_constants/outline_size = 3
theme_override_font_sizes/font_size = 39
text = "press any key to start"
horizontal_alignment = 1
@ -202,27 +182,33 @@ texture = ExtResource("5_logo")
expand_mode = 1
stretch_mode = 5
[node name="VersionRow" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/BrandingSlot/BrandingMotionRoot/BrandingContent"]
[node name="VersionRow" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 18)
layout_mode = 2
z_index = 12
custom_minimum_size = Vector2(280, 28)
layout_mode = 1
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -140.0
offset_top = -38.0
offset_right = 140.0
offset_bottom = -10.0
grow_horizontal = 2
grow_vertical = 0
mouse_filter = 2
[node name="VersionAnchor" type="CenterContainer" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/BrandingSlot/BrandingMotionRoot/BrandingContent/VersionRow"]
[node name="VersionAnchor" type="CenterContainer" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/VersionRow"]
layout_mode = 1
anchor_left = 0.7
anchor_top = 0.5
anchor_right = 0.7
anchor_bottom = 0.5
offset_left = -140.0
offset_top = -17.0
offset_right = 140.0
offset_bottom = 11.0
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="PlaytestLabel" type="Label" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/BrandingSlot/BrandingMotionRoot/BrandingContent/VersionRow/VersionAnchor"]
[node name="PlaytestLabel" type="Label" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/VersionRow/VersionAnchor"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1)
@ -242,47 +228,75 @@ layout_mode = 2
[node name="BubbleLayoutSlot" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter"]
unique_name_in_owner = true
custom_minimum_size = Vector2(440, 318)
custom_minimum_size = Vector2(500, 360)
layout_mode = 2
[node name="BubbleMotionRoot" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot"]
unique_name_in_owner = true
layout_mode = 0
offset_right = 440.0
offset_bottom = 318.0
offset_right = 500.0
offset_bottom = 360.0
mouse_filter = 2
[node name="BubbleField" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot"]
unique_name_in_owner = true
custom_minimum_size = Vector2(440, 318)
custom_minimum_size = Vector2(500, 360)
layout_mode = 0
offset_right = 440.0
offset_bottom = 318.0
offset_right = 500.0
offset_top = -10.0
offset_bottom = 350.0
script = ExtResource("8_bubble_cluster")
profile = ExtResource("9_bubble_profile")
profile = ExtResource("23_title_menu_profile")
desktop_reference_size = Vector2(500, 360)
collision_separation_enabled = false
[node name="PlayButton" type="Button" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField"]
unique_name_in_owner = true
offset_left = 106.0
offset_top = 71.0
offset_right = 290.0
offset_bottom = 249.0
offset_left = 36.0
offset_top = 30.0
offset_right = 366.0
offset_bottom = 360.0
texture_filter = 1
icon = ExtResource("16_continue")
tooltip_text = "play"
accessibility_name = "play"
accessibility_name = "play straywild"
script = ExtResource("7_bubble_button")
profile = ExtResource("9_bubble_profile")
neutral_size = Vector2(184, 178)
desktop_anchor = Vector2(198, 160)
compact_anchor = Vector2(198, 164)
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
profile = ExtResource("23_title_menu_profile")
neutral_size = Vector2(330, 330)
desktop_anchor = Vector2(201, 195)
compact_anchor = Vector2(201, 195)
minimum_font_size = 28
maximum_font_size = 36
icon_fill_ratio_override = 0.9
horizontal_amplitude = 0.0
vertical_amplitude = 0.0
deformation_amplitude = 0.0
hover_wiggle_amplitude = Vector2(7, 6)
hover_wiggle_period = 3.8
hover_drawer_text = "play straywild"
hover_drawer_mode = 1
elliptical_hit_area = false
[node name="ContinueStatsDrawer" type="RichTextLabel" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField/PlayButton"]
unique_name_in_owner = true
show_behind_parent = true
z_index = -1
visible = false
layout_mode = 0
offset_left = 22.0
offset_top = 314.0
offset_right = 308.0
offset_bottom = 360.0
pivot_offset = Vector2(143, 0)
scale = Vector2(1, 0.001)
mouse_filter = 2
theme_override_font_sizes/normal_font_size = 15
text = ""
bbcode_enabled = true
scroll_active = false
autowrap_mode = 2
horizontal_alignment = 1
vertical_alignment = 1
theme_override_styles/normal = SubResource("StyleBoxFlat_continue_stats_drawer")
[node name="NewGameButton" type="Button" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField"]
unique_name_in_owner = true
@ -313,75 +327,75 @@ deformation_period = 5.9
[node name="SettingsButton" type="Button" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField"]
unique_name_in_owner = true
offset_left = 254.0
offset_top = 12.0
offset_right = 374.0
offset_bottom = 128.0
offset_left = 384.0
offset_top = 116.0
offset_right = 456.0
offset_bottom = 188.0
texture_filter = 1
icon = ExtResource("13_settings_dark")
tooltip_text = "settings"
accessibility_name = "settings"
script = ExtResource("7_bubble_button")
profile = ExtResource("9_bubble_profile")
desktop_anchor = Vector2(314, 70)
compact_anchor = Vector2(333, 75)
compact_minimum_size = Vector2(94, 90)
profile = ExtResource("24_title_secondary_menu_profile")
neutral_size = Vector2(72, 72)
desktop_anchor = Vector2(420, 152)
compact_anchor = Vector2(420, 152)
minimum_font_size = 15
maximum_font_size = 22
vertical_amplitude = 3.7
motion_period = 4.9
motion_phase = 2.5
deformation_period = 6.3
maximum_font_size = 20
horizontal_amplitude = 0.0
vertical_amplitude = 0.0
deformation_amplitude = 0.0
hover_wiggle_amplitude = Vector2(2.4, 1.2)
hover_wiggle_period = 1.9
hover_drawer_text = "settings"
elliptical_hit_area = false
[node name="CreditsButton" type="Button" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField"]
unique_name_in_owner = true
offset_left = 54.0
offset_top = 209.0
offset_right = 138.0
offset_bottom = 291.0
offset_left = 384.0
offset_top = 202.0
offset_right = 456.0
offset_bottom = 274.0
texture_filter = 1
icon = ExtResource("18_credits")
tooltip_text = "credits"
accessibility_name = "credits"
script = ExtResource("7_bubble_button")
profile = ExtResource("9_bubble_profile")
neutral_size = Vector2(84, 82)
desktop_anchor = Vector2(96, 250)
compact_anchor = Vector2(82, 258)
compact_minimum_size = Vector2(62, 62)
minimum_font_size = 12
maximum_font_size = 17
horizontal_amplitude = 1.4
vertical_amplitude = 3.0
motion_period = 5.0
motion_phase = 1.8
deformation_amplitude = 0.015
deformation_period = 5.6
profile = ExtResource("24_title_secondary_menu_profile")
neutral_size = Vector2(72, 72)
desktop_anchor = Vector2(420, 238)
compact_anchor = Vector2(420, 238)
minimum_font_size = 15
maximum_font_size = 20
horizontal_amplitude = 0.0
vertical_amplitude = 0.0
deformation_amplitude = 0.0
hover_wiggle_amplitude = Vector2(2.4, 1.2)
hover_wiggle_period = 1.9
hover_drawer_text = "credits"
elliptical_hit_area = false
[node name="JoinGameButton" type="Button" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField"]
unique_name_in_owner = true
offset_left = 21.0
offset_top = 11.0
offset_right = 143.0
offset_bottom = 129.0
offset_left = 384.0
offset_top = 30.0
offset_right = 456.0
offset_bottom = 102.0
texture_filter = 1
icon = ExtResource("12_online_dark")
tooltip_text = "join game"
accessibility_name = "join game"
accessibility_name = "online"
script = ExtResource("7_bubble_button")
profile = ExtResource("9_bubble_profile")
neutral_size = Vector2(122, 118)
desktop_anchor = Vector2(82, 70)
compact_anchor = Vector2(63, 75)
compact_minimum_size = Vector2(82, 80)
profile = ExtResource("24_title_secondary_menu_profile")
neutral_size = Vector2(72, 72)
desktop_anchor = Vector2(420, 66)
compact_anchor = Vector2(420, 66)
minimum_font_size = 15
maximum_font_size = 22
horizontal_amplitude = 1.7
vertical_amplitude = 3.8
motion_period = 5.2
motion_phase = 3.1
deformation_amplitude = 0.015
deformation_period = 5.7
maximum_font_size = 20
horizontal_amplitude = 0.0
vertical_amplitude = 0.0
deformation_amplitude = 0.0
hover_wiggle_amplitude = Vector2(2.4, 1.2)
hover_wiggle_period = 1.9
hover_drawer_text = "online"
elliptical_hit_area = false
[node name="DeleteSaveButton" type="Button" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField"]
unique_name_in_owner = true
@ -411,35 +425,41 @@ deformation_period = 5.4
[node name="QuitButton" type="Button" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField"]
unique_name_in_owner = true
offset_left = 255.0
offset_top = 204.0
offset_right = 349.0
offset_bottom = 296.0
offset_left = 384.0
offset_top = 288.0
offset_right = 456.0
offset_bottom = 360.0
texture_filter = 1
icon = ExtResource("14_x_dark")
tooltip_text = "quit"
accessibility_name = "quit"
accessibility_name = "close"
script = ExtResource("7_bubble_button")
profile = ExtResource("9_bubble_profile")
neutral_size = Vector2(94, 92)
desktop_anchor = Vector2(302, 250)
compact_anchor = Vector2(317, 254)
compact_minimum_size = Vector2(68, 68)
profile = ExtResource("24_title_secondary_menu_profile")
neutral_size = Vector2(72, 72)
desktop_anchor = Vector2(420, 324)
compact_anchor = Vector2(420, 324)
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
maximum_font_size = 20
horizontal_amplitude = 0.0
vertical_amplitude = 0.0
deformation_amplitude = 0.0
hover_wiggle_amplitude = Vector2(2.4, 1.2)
hover_wiggle_period = 1.9
hover_drawer_text = "close"
elliptical_hit_area = false
[node name="FeedbackLabel" type="RichTextLabel" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent"]
[node name="FeedbackLabel" type="RichTextLabel" parent="ResponsiveTitleStage/TitlePresentationScaleRoot"]
unique_name_in_owner = true
z_index = 12
visible = false
custom_minimum_size = Vector2(430, 44)
layout_mode = 2
size_flags_horizontal = 4
custom_minimum_size = Vector2(500, 44)
layout_mode = 1
anchor_left = 0.5
anchor_right = 0.5
offset_left = -250.0
offset_top = 224.0
offset_right = 250.0
offset_bottom = 268.0
grow_horizontal = 2
text = ""
bbcode_enabled = true
scroll_active = false

View file

@ -1,97 +0,0 @@
shader_type canvas_item;
uniform vec4 surface_color : source_color = vec4(0.10, 0.45, 0.62, 1.0);
uniform vec4 middle_color : source_color = vec4(0.055, 0.285, 0.40, 1.0);
uniform vec4 depth_color : source_color = vec4(0.02, 0.16, 0.27, 1.0);
uniform vec2 virtual_pixel_density = vec2(320.0, 180.0);
uniform float color_step_count : hint_range(2.0, 16.0, 1.0) = 14.0;
uniform float continuous_depth_blend : hint_range(0.0, 0.25, 0.01) = 0.12;
uniform float wave_speed : hint_range(0.0, 0.5, 0.01) = 0.10;
uniform float wave_scale : hint_range(0.5, 8.0, 0.1) = 2.4;
uniform float wave_strength : hint_range(0.0, 0.05, 0.001) = 0.012;
uniform float caustic_strength : hint_range(0.0, 0.08, 0.001) = 0.018;
uniform bool animation_enabled = true;
void fragment() {
vec2 grid_size = max(virtual_pixel_density, vec2(1.0));
vec2 pixel_uv = (floor(UV * grid_size) + vec2(0.5)) / grid_size;
float continuous_depth = pixel_uv.y;
float caustic_level = 0.0;
if (animation_enabled) {
float motion = TIME * wave_speed;
float broad_wave = sin(
pixel_uv.x * wave_scale + motion
);
float secondary_wave = sin(
pixel_uv.x * 1.1 + pixel_uv.y * 1.6 - motion * 0.65
);
float movement_falloff = 1.0 - pixel_uv.y * 0.75;
float depth_displacement = (
(broad_wave * 0.65 + secondary_wave * 0.35)
* wave_strength
* movement_falloff
);
continuous_depth = clamp(
pixel_uv.y
+ depth_displacement,
0.0,
1.0
);
float caustic_wave = clamp(
(
sin(
pixel_uv.x * 4.0
+ pixel_uv.y * 1.2
+ motion * 0.7
)
* sin(
pixel_uv.x * 1.7
- pixel_uv.y * 2.2
- motion * 0.55
)
- 0.25
) / 0.75,
0.0,
1.0
);
caustic_level = floor(caustic_wave * 3.0) / 3.0;
}
float depth_steps = max(2.0, floor(color_step_count));
float band_index = floor(
continuous_depth * (depth_steps - 1.0) + 0.5
);
float band_depth = band_index / (depth_steps - 1.0);
float display_depth = mix(
band_depth,
continuous_depth,
clamp(continuous_depth_blend, 0.0, 0.25)
);
vec3 water_color;
if (display_depth < 0.5) {
water_color = mix(
surface_color.rgb,
middle_color.rgb,
display_depth * 2.0
);
} else {
water_color = mix(
middle_color.rgb,
depth_color.rgb,
(display_depth - 0.5) * 2.0
);
}
float upper_water_mask = 1.0 - smoothstep(
0.18,
0.34,
pixel_uv.y
);
water_color += (
vec3(0.05, 0.12, 0.14)
* caustic_level
* upper_water_mask
* caustic_strength
);
COLOR = vec4(clamp(water_color, vec3(0.0), vec3(1.0)), 1.0);
}

View file

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