Refine character animation and fishing presentation

This commit is contained in:
Alexander Sellite 2026-08-01 11:16:21 -04:00
parent 81f5010ad2
commit c596b2aee7
15 changed files with 403 additions and 402 deletions

110
ui/emote_radial_menu.gd Normal file
View file

@ -0,0 +1,110 @@
class_name EmoteRadialMenu
extends Control
signal emote_selected(emote_id: StringName)
const BubbleButtonScene: PackedScene = preload(
"res://ui/components/bubble_menu/bubble_button.tscn"
)
const BubbleProfile: BubbleMenuProfile = preload(
"res://ui/components/bubble_menu/bubble_menu_profile.tres"
)
const SECTOR_COUNT: int = 8
const SIT_SECTOR: int = 0
const RING_RADIUS: float = 172.0
const BUBBLE_SIZE: Vector2 = Vector2(92.0, 88.0)
var _buttons: Array[BubbleButton] = []
var _is_open: bool = false
var _selected_sector: int = SIT_SECTOR
func _ready() -> void:
visible = false
mouse_filter = Control.MOUSE_FILTER_IGNORE
for sector: int in SECTOR_COUNT:
var bubble: BubbleButton = BubbleButtonScene.instantiate() as BubbleButton
bubble.profile = BubbleProfile
bubble.focus_mode = Control.FOCUS_NONE
bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE
bubble.text = "Sit" if sector == SIT_SECTOR else ""
add_child(bubble)
_buttons.append(bubble)
_apply_selection_styles()
func handle_input(event: InputEvent, can_open: bool) -> bool:
var key_event: InputEventKey = event as InputEventKey
if key_event == null or key_event.echo:
return false
if key_event.physical_keycode != KEY_C:
return false
if key_event.pressed:
if _is_open or not can_open:
return _is_open
open_menu()
return true
if not _is_open:
return false
var selected: int = _selected_sector
close_menu()
if selected == SIT_SECTOR:
emote_selected.emit(&"sit")
return true
func open_menu() -> void:
_is_open = true
_selected_sector = SIT_SECTOR
visible = true
_layout_bubbles()
_apply_selection_styles()
func close_menu() -> void:
_is_open = false
visible = false
func is_open() -> bool:
return _is_open
func _process(_delta: float) -> void:
if not _is_open:
return
_layout_bubbles()
_update_mouse_selection()
func _layout_bubbles() -> void:
var center: Vector2 = size * 0.5
for sector: int in SECTOR_COUNT:
var angle: float = -PI * 0.5 + TAU * float(sector) / float(SECTOR_COUNT)
var bubble_center: Vector2 = center + Vector2.from_angle(angle) * RING_RADIUS
var bubble: BubbleButton = _buttons[sector]
bubble.position = bubble_center - BUBBLE_SIZE * 0.5
bubble.size = BUBBLE_SIZE
bubble.pivot_offset = BUBBLE_SIZE * 0.5
func _update_mouse_selection() -> void:
var offset: Vector2 = get_local_mouse_position() - size * 0.5
if offset.length() < 24.0:
return
var angle: float = fposmod(offset.angle() + PI * 0.5 + PI / 8.0, TAU)
var sector: int = int(floor(angle / (TAU / float(SECTOR_COUNT))))
if sector == _selected_sector:
return
_selected_sector = sector
_apply_selection_styles()
func _apply_selection_styles() -> void:
for sector: int in _buttons.size():
var bubble: BubbleButton = _buttons[sector]
bubble.apply_profile()
if sector == _selected_sector:
bubble.add_theme_stylebox_override(
"normal", BubbleProfile.make_hover_style()
)

View file

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

14
ui/emote_radial_menu.tscn Normal file
View file

@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://ui/emote_radial_menu.gd" id="1_emote"]
[node name="EmoteRadialMenu" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
z_index = 90
script = ExtResource("1_emote")

View file

@ -32,6 +32,7 @@ const PlayerCoolerCapacityType = preload(
"res://progression/player_cooler_capacity.gd"
)
const ChatUIType = preload("res://ui/chat_ui.gd")
const EmoteRadialMenuType = preload("res://ui/emote_radial_menu.gd")
const PlayerSettingsManagerType = preload(
"res://settings/player_settings_manager.gd"
)
@ -49,7 +50,8 @@ signal shop_backdrop_visibility_changed(is_visible: bool)
@onready var _green_catch_progress: ProgressBar = %GreenCatchProgress
@onready var _red_chase_progress: ProgressBar = %RedChaseProgress
@onready var _barrier_markers: Control = %BarrierMarkers
@onready var _barrier_summary: Label = %BarrierSummary
@onready var _barrier_prompt_panel: PanelContainer = %BarrierPromptPanel
@onready var _barrier_prompt: Label = %BarrierPrompt
@onready var _barrier_health: Label = %BarrierHealth
@onready var _showcase_details: Label = %ShowcaseDetails
@onready var _fishing_panel: PanelContainer = %FishingPanel
@ -62,6 +64,7 @@ signal shop_backdrop_visibility_changed(is_visible: bool)
@onready var _shop_prompt: PanelContainer = %ShopPrompt
@onready var _effect_status: Label = %EffectStatus
@onready var _chat_ui: ChatUIType = %ChatUI
@onready var _emote_radial_menu: EmoteRadialMenuType = %EmoteRadialMenu
@onready var _title_settings_panel: SettingsPanelType = (
$UIRoot/TitleScreen/ResponsiveTitleStage/TitlePresentationScaleRoot/SettingsPanel
)
@ -70,6 +73,7 @@ signal shop_backdrop_visibility_changed(is_visible: bool)
)
var _showcase_active: bool = false
var _player: PlayerType
var _player_menu_open: bool = false
var _gameplay_ui_enabled: bool = false
var _fishing_spot: FishingSpotType
@ -83,6 +87,7 @@ var _shop_interaction: ShopInteractionType
func _ready() -> void:
_emote_radial_menu.emote_selected.connect(_on_emote_selected)
_chat_ui.text_entry_ownership_changed.connect(
_on_chat_text_entry_ownership_changed
)
@ -132,6 +137,7 @@ func setup(
network_player_list: NetworkPlayerListService,
settings_manager: PlayerSettingsManagerType,
) -> void:
_player = player
_fishing_spot = fishing_spot
_item_effects = item_effects
_chat_ui.setup(
@ -202,6 +208,26 @@ func setup(
_shop_interaction = shop_interaction
func _input(event: InputEvent) -> void:
if _emote_radial_menu == null:
return
var can_open: bool = (
_gameplay_ui_enabled
and not _system_menu_open
and not _player_menu_open
and not _shop_open
and not _chat_input_open
and not _showcase_active
)
if _emote_radial_menu.handle_input(event, can_open):
get_viewport().set_input_as_handled()
func _on_emote_selected(emote_id: StringName) -> void:
if emote_id == &"sit" and _player != null:
_player.toggle_sitting()
func setup_data_and_identity(
data_root: PlayerDataRoot,
identity_backups: IdentityBackupService,
@ -359,6 +385,17 @@ func _on_fishing_status_changed(status: String) -> void:
return
if _fishing_spot != null and _fishing_spot.is_fighting():
return
var normalized_status: String = status.strip_edges().to_lower()
if normalized_status in [
"fishing cancelled.",
"fishing cancelled",
"fishing failed.",
"fishing failed",
"fishing attempt ended.",
"fishing attempt ended",
]:
_set_fishing_status("")
return
_set_fishing_status(status)
@ -373,9 +410,6 @@ func _refresh_fishing_panel_visibility() -> void:
var has_content: bool = (
not _status_label.text.strip_edges().is_empty()
or not _showcase_details.text.strip_edges().is_empty()
or _catch_track.visible
or _barrier_summary.visible
or _barrier_health.visible
)
_fishing_panel.visible = (
_gameplay_ui_enabled
@ -388,7 +422,7 @@ func _on_catch_display_changed(
chase_progress: float,
barrier_positions: PackedFloat32Array,
barrier_health: PackedInt32Array,
barrier_max_health: PackedInt32Array,
_barrier_max_health: PackedInt32Array,
active_barrier_index: int,
visible: bool,
) -> void:
@ -401,12 +435,13 @@ func _on_catch_display_changed(
_green_catch_progress.value = progress * 100.0
_red_chase_progress.value = maxf(chase_progress, 0.0) * 100.0
_catch_track.visible = encounter_visible
_barrier_summary.visible = encounter_visible
_barrier_health.visible = encounter_visible
_barrier_prompt_panel.visible = false
_barrier_prompt.visible = false
_barrier_health.visible = false
if not encounter_visible:
_barrier_health.visible = false
_barrier_prompt.visible = false
_clear_barrier_markers()
_barrier_summary.text = ""
_barrier_health.text = ""
_refresh_fishing_panel_visibility()
return
@ -418,45 +453,18 @@ func _on_catch_display_changed(
barrier_health,
active_barrier_index
)
var barrier_labels: PackedStringArray = []
for barrier_index: int in range(barrier_positions.size()):
var defeated: bool = (
barrier_index < barrier_health.size()
and barrier_health[barrier_index] <= 0
)
var marker: String = "" if defeated else ""
if barrier_index == active_barrier_index:
marker = "[%s]" % marker
barrier_labels.append(
"%d%% %s"
% [roundi(barrier_positions[barrier_index] * 100.0), marker]
)
_barrier_summary.text = "barriers: %s" % " ".join(barrier_labels)
if (
active_barrier_index >= 0
and active_barrier_index < barrier_health.size()
and active_barrier_index < barrier_max_health.size()
):
_barrier_health.text = (
"barrier: %d / %d"
% [
barrier_health[active_barrier_index],
barrier_max_health[active_barrier_index],
]
)
_barrier_prompt_panel.visible = true
_barrier_prompt.visible = true
_barrier_health.visible = true
_barrier_health.text = "%d" % barrier_health[active_barrier_index]
else:
_barrier_health.text = "hold left click to reel"
var chase_gap: float = progress - chase_progress
if chase_gap <= 0.05:
_barrier_health.text = (
"%s%s"
% [
_barrier_health.text,
"\nred is closing in!",
]
).strip_edges()
_barrier_prompt.visible = false
_barrier_health.visible = false
_barrier_health.text = ""
_refresh_fishing_panel_visibility()
@ -508,15 +516,18 @@ func _on_showcase_changed(
_set_fishing_status("")
return
_catch_track.visible = false
_barrier_summary.visible = false
_barrier_prompt_panel.visible = false
_barrier_prompt.visible = false
_barrier_health.visible = false
_clear_barrier_markers()
_showcase_details.text = (
"%.1f lb • %s\nleft click or escape to put away"
"%.1f lb • %s"
% [weight_lb, rarity_name]
)
_showcase_details.visible = true
_set_fishing_status("you caught a %s!" % fish_name)
_status_label.text = "You caught a %s!" % fish_name
_status_label.visible = true
_refresh_fishing_panel_visibility()
func _on_player_menu_visibility_changed(is_open: bool) -> void:

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=14 format=3]
[gd_scene load_steps=15 format=3]
[ext_resource type="Script" path="res://ui/game_ui.gd" id="1_ui"]
[ext_resource type="PackedScene" path="res://ui/player_menu.tscn" id="2_menu"]
@ -9,27 +9,27 @@
[ext_resource type="PackedScene" path="res://ui/hotbar.tscn" id="7_hotbar"]
[ext_resource type="PackedScene" path="res://ui/fishing_shop.tscn" id="8_shop"]
[ext_resource type="Script" path="res://ui/chat_ui.gd" id="9_chat"]
[ext_resource type="PackedScene" path="res://ui/emote_radial_menu.tscn" id="10_emote"]
[sub_resource type="StyleBoxFlat" id="StyleBox_chase_background"]
bg_color = Color(0.055, 0.105, 0.125, 1)
corner_radius_top_left = 6
corner_radius_top_right = 6
corner_radius_bottom_right = 6
corner_radius_bottom_left = 6
[sub_resource type="StyleBoxFlat" id="StyleBox_chase_fill"]
bg_color = Color(0.937, 0.357, 0.384, 0.95)
corner_radius_top_left = 6
corner_radius_top_right = 6
corner_radius_bottom_right = 6
corner_radius_bottom_left = 6
[sub_resource type="StyleBoxFlat" id="StyleBox_catch_fill"]
bg_color = Color(0.275, 0.784, 0.471, 0.92)
corner_radius_top_left = 6
corner_radius_top_right = 6
corner_radius_bottom_right = 6
corner_radius_bottom_left = 6
[sub_resource type="StyleBoxFlat" id="StyleBox_fishing_panel"]
bg_color = Color(0.055, 0.105, 0.125, 0.96)
border_width_left = 0
border_width_top = 0
border_width_right = 0
border_width_bottom = 0
corner_radius_top_left = 12
corner_radius_top_right = 12
corner_radius_bottom_right = 12
corner_radius_bottom_left = 12
[sub_resource type="StyleBoxFlat" id="StyleBox_transparent"]
bg_color = Color(0, 0, 0, 0)
@ -64,23 +64,24 @@ anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -205.0
offset_top = -216.0
offset_right = 205.0
offset_bottom = -128.0
offset_left = -260.0
offset_top = -150.0
offset_right = 260.0
offset_bottom = -68.0
grow_horizontal = 2
grow_vertical = 0
mouse_filter = 2
theme = ExtResource("3_theme")
theme_override_styles/panel = SubResource("StyleBox_fishing_panel")
[node name="MarginContainer" type="MarginContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel"]
theme_override_constants/margin_left = 14
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 14
theme_override_constants/margin_bottom = 8
theme_override_constants/margin_left = 0
theme_override_constants/margin_top = 0
theme_override_constants/margin_right = 0
theme_override_constants/margin_bottom = 0
[node name="VBoxContainer" type="VBoxContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer"]
theme_override_constants/separation = 4
theme_override_constants/separation = 2
[node name="StatusLabel" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
@ -94,12 +95,25 @@ unique_name_in_owner = true
visible = false
horizontal_alignment = 1
[node name="CatchTrack" type="Control" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
[node name="CatchTrack" type="Control" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
unique_name_in_owner = true
visible = false
custom_minimum_size = Vector2(0, 20)
z_index = 60
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -420.0
offset_top = -160.0
offset_right = 420.0
offset_bottom = -124.0
grow_horizontal = 2
grow_vertical = 0
mouse_filter = 2
[node name="GreenCatchProgress" type="ProgressBar" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
[node name="GreenCatchProgress" type="ProgressBar" parent="UIRoot/CanonicalStage/GameplayTransientHUD/CatchTrack"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
@ -113,7 +127,7 @@ theme_override_styles/fill = SubResource("StyleBox_catch_fill")
value = 0.0
show_percentage = false
[node name="RedChaseProgress" type="ProgressBar" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
[node name="RedChaseProgress" type="ProgressBar" parent="UIRoot/CanonicalStage/GameplayTransientHUD/CatchTrack"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
@ -127,7 +141,7 @@ theme_override_styles/fill = SubResource("StyleBox_chase_fill")
value = 0.0
show_percentage = false
[node name="BarrierMarkers" type="Control" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer/CatchTrack"]
[node name="BarrierMarkers" type="Control" parent="UIRoot/CanonicalStage/GameplayTransientHUD/CatchTrack"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
@ -137,15 +151,51 @@ grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="BarrierSummary" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
[node name="BarrierPromptPanel" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
unique_name_in_owner = true
visible = false
horizontal_alignment = 1
z_index = 61
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -170.0
offset_top = -116.0
offset_right = 170.0
offset_bottom = -8.0
grow_horizontal = 2
grow_vertical = 0
pivot_offset = Vector2(170, 55)
rotation = 0.025
mouse_filter = 2
theme = ExtResource("3_theme")
theme_override_styles/panel = SubResource("StyleBox_fishing_panel")
[node name="BarrierHealth" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
[node name="Margin" type="MarginContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/BarrierPromptPanel"]
theme_override_constants/margin_left = 18
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 18
theme_override_constants/margin_bottom = 4
[node name="Layout" type="VBoxContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD/BarrierPromptPanel/Margin"]
theme_override_constants/separation = 0
[node name="BarrierPrompt" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/BarrierPromptPanel/Margin/Layout"]
unique_name_in_owner = true
visible = false
text = "MASH!"
horizontal_alignment = 1
theme_override_colors/font_color = Color(1, 0.84, 0.3, 1)
theme_override_font_sizes/font_size = 63
[node name="BarrierHealth" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/BarrierPromptPanel/Margin/Layout"]
unique_name_in_owner = true
visible = false
text = "0"
horizontal_alignment = 1
theme_override_colors/font_color = Color(0.95, 0.98, 1, 1)
theme_override_font_sizes/font_size = 34
[node name="PlayerMenu" parent="UIRoot/CanonicalStage" instance=ExtResource("2_menu")]
unique_name_in_owner = true
@ -153,6 +203,9 @@ unique_name_in_owner = true
[node name="Hotbar" parent="UIRoot/CanonicalStage" instance=ExtResource("7_hotbar")]
unique_name_in_owner = true
[node name="EmoteRadialMenu" parent="UIRoot/CanonicalStage" instance=ExtResource("10_emote")]
unique_name_in_owner = true
[node name="ShopPrompt" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD"]
unique_name_in_owner = true
visible = false