From bc47d47149980950bf200ece56e1f5bd1b925f1b Mon Sep 17 00:00:00 2001 From: WgLy <102111george@gmail.com> Date: Sat, 18 Jul 2026 01:10:27 +0800 Subject: [PATCH 1/2] feat: add demo result screen --- Scenes/demo.tscn | 6 +- Scenes/demo_result_screen.tscn | 219 ++++++++++++++++++++++++++++ Scripts/demo_renderer.gd | 39 ++++- Scripts/demo_result_screen.gd | 215 +++++++++++++++++++++++++++ Scripts/demo_result_screen.gd.uid | 1 + Scripts/network/state_serializer.gd | 5 + 6 files changed, 483 insertions(+), 2 deletions(-) create mode 100644 Scenes/demo_result_screen.tscn create mode 100644 Scripts/demo_result_screen.gd create mode 100644 Scripts/demo_result_screen.gd.uid diff --git a/Scenes/demo.tscn b/Scenes/demo.tscn index 184f54b9..7d96a85a 100644 --- a/Scenes/demo.tscn +++ b/Scenes/demo.tscn @@ -1,4 +1,4 @@ -[gd_scene format=4 uid="uid://cwk1s3v2wvxo3"] +[gd_scene format=4 uid="uid://7c4jde8i3gyh"] [ext_resource type="Script" uid="uid://dwpnag6ymsmow" path="res://Scripts/demo_renderer.gd" id="1_demo"] [ext_resource type="Texture2D" uid="uid://c1rj0im3pw7gd" path="res://Shapes/sand_background.png" id="1_glc8m"] @@ -10,6 +10,7 @@ [ext_resource type="FontFile" uid="uid://hp5u4fxb5ifw" path="res://Assets/fonts/PixeloidSans-Bold.ttf" id="4_font"] [ext_resource type="Texture2D" uid="uid://dle4xt8upfg42" path="res://Shapes/Square.svg" id="4_qdju7"] [ext_resource type="Script" uid="uid://d2y578q176xkm" path="res://Scripts/high_stage.gd" id="5_high_stage"] +[ext_resource type="PackedScene" uid="uid://dao1hqwdldnlw" path="res://Scenes/demo_result_screen.tscn" id="6_a1i4p"] [ext_resource type="Texture2D" uid="uid://f5ertr28h6kq" path="res://Shapes/feather.svg" id="6_feather"] [ext_resource type="Script" uid="uid://cxd17lrphypsv" path="res://Scripts/game_camera.gd" id="7_camera"] @@ -110,6 +111,7 @@ font_size = 56 font = ExtResource("4_font") font_size = 36 + [node name="DemoMain" type="Control" unique_id=1296044060] layout_mode = 3 anchors_preset = 15 @@ -589,3 +591,5 @@ high_stage_a = NodePath("../HigherThanPlayerA/Stage") high_stage_b = NodePath("../HigherThanPlayerB/Stage") camera_a = NodePath("../ScreenA/SubViewport/Camera2D") camera_b = NodePath("../ScreenB/SubViewport/Camera2D") + +[node name="DemoResultScreen" parent="." unique_id=250910659 instance=ExtResource("6_a1i4p")] diff --git a/Scenes/demo_result_screen.tscn b/Scenes/demo_result_screen.tscn new file mode 100644 index 00000000..a02ec422 --- /dev/null +++ b/Scenes/demo_result_screen.tscn @@ -0,0 +1,219 @@ +[gd_scene format=3 uid="uid://dao1hqwdldnlw"] + +[ext_resource type="Script" uid="uid://dltobunw2epqg" path="res://Scripts/demo_result_screen.gd" id="1_dcoi0"] +[ext_resource type="Texture2D" uid="uid://du0qdbs7y7rcf" path="res://Shapes/result_background.JPG" id="2_2qkro"] +[ext_resource type="FontFile" uid="uid://hp5u4fxb5ifw" path="res://Assets/fonts/PixeloidSans-Bold.ttf" id="3_5u2eh"] +[ext_resource type="Script" uid="uid://ddqb3djaesce4" path="res://Scripts/health_icon.gd" id="4_6ykjf"] +[ext_resource type="Texture2D" uid="uid://f5ertr28h6kq" path="res://Shapes/feather.svg" id="5_56pta"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ib03x"] +bg_color = Color(0.27450982, 0.27450982, 0.23529412, 0.7019608) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_n8x6i"] +bg_color = Color(0, 0, 0, 0) +border_width_left = 3 +border_width_top = 3 +border_width_right = 3 +border_width_bottom = 3 +border_color = Color(1, 1, 1, 0.92156863) +corner_radius_top_left = 26 +corner_radius_top_right = 27 +corner_radius_bottom_right = 26 +corner_radius_bottom_left = 27 + +[sub_resource type="StyleBoxLine" id="StyleBoxLine_qk7ap"] +color = Color(1, 1, 1, 1) +thickness = 3 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qk7ap"] +bg_color = Color(0.27450982, 0.1764706, 0.078431375, 1) +corner_radius_top_left = 6 +corner_radius_top_right = 7 +corner_radius_bottom_right = 6 +corner_radius_bottom_left = 7 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_o517f"] +bg_color = Color(0.18431373, 0.12156863, 0.07058824, 1) +corner_radius_top_left = 6 +corner_radius_top_right = 7 +corner_radius_bottom_right = 6 +corner_radius_bottom_left = 7 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ndexe"] +bg_color = Color(0.33333334, 0.21568628, 0.101960786, 1) +corner_radius_top_left = 6 +corner_radius_top_right = 7 +corner_radius_bottom_right = 6 +corner_radius_bottom_left = 7 + +[node name="DemoResultScreen" type="CanvasLayer" unique_id=250910659] +process_mode = 3 +layer = 100 +visible = false +script = ExtResource("1_dcoi0") + +[node name="ResultRoot" type="Control" parent="." unique_id=80173994] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Background" type="TextureRect" parent="ResultRoot" unique_id=50513428] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -373.0 +offset_top = -130.0 +offset_right = 141.0 +offset_bottom = -1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +texture = ExtResource("2_2qkro") +expand_mode = 2 +stretch_mode = 5 + +[node name="CenterContainer" type="CenterContainer" parent="ResultRoot" unique_id=863430935] +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="Panel" type="Panel" parent="ResultRoot/CenterContainer" unique_id=1170119724] +custom_minimum_size = Vector2(1152, 648) +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_ib03x") + +[node name="PanelContainer" type="PanelContainer" parent="ResultRoot/CenterContainer" unique_id=1914732084] +custom_minimum_size = Vector2(750, 420) +layout_mode = 2 +mouse_filter = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_n8x6i") + +[node name="MarginContainer" type="MarginContainer" parent="ResultRoot/CenterContainer/PanelContainer" unique_id=533272811] +layout_mode = 2 +mouse_filter = 2 +theme_override_constants/margin_left = 44 +theme_override_constants/margin_top = 32 +theme_override_constants/margin_right = 44 +theme_override_constants/margin_bottom = 32 + +[node name="VBoxContainer" type="VBoxContainer" parent="ResultRoot/CenterContainer/PanelContainer/MarginContainer" unique_id=94432051] +unique_name_in_owner = true +layout_mode = 2 +mouse_filter = 2 +theme_override_constants/separation = 14 + +[node name="TitleLabel" type="Label" parent="ResultRoot/CenterContainer/PanelContainer/MarginContainer/VBoxContainer" unique_id=1068592199] +unique_name_in_owner = true +layout_mode = 2 +theme_override_fonts/font = ExtResource("3_5u2eh") +theme_override_font_sizes/font_size = 36 +text = "RESULT" +horizontal_alignment = 1 + +[node name="Separator" type="HSeparator" parent="ResultRoot/CenterContainer/PanelContainer/MarginContainer/VBoxContainer" unique_id=412927854] +layout_mode = 2 +theme_override_styles/separator = SubResource("StyleBoxLine_qk7ap") + +[node name="HBoxContainer" type="HBoxContainer" parent="ResultRoot/CenterContainer/PanelContainer/MarginContainer/VBoxContainer" unique_id=224187305] +layout_mode = 2 +theme_override_constants/separation = 40 + +[node name="StatNamesLabel" type="Label" parent="ResultRoot/CenterContainer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer" unique_id=635769938] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/line_spacing = 7 +theme_override_fonts/font = ExtResource("3_5u2eh") +theme_override_font_sizes/font_size = 22 +text = "Survival Time +Remaining Health +Energy Balls Collected +Jumps +Distance Traveled +Total Energy Spent" + +[node name="StatValuesALabel" type="Label" parent="ResultRoot/CenterContainer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer" unique_id=119839190] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/line_spacing = 7 +theme_override_fonts/font = ExtResource("3_5u2eh") +theme_override_font_sizes/font_size = 22 +horizontal_alignment = 2 + +[node name="StatValuesBLabel" type="Label" parent="ResultRoot/CenterContainer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer" unique_id=1618690426] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/line_spacing = 7 +theme_override_fonts/font = ExtResource("3_5u2eh") +theme_override_font_sizes/font_size = 22 +horizontal_alignment = 2 + +[node name="spacer" type="Control" parent="ResultRoot/CenterContainer/PanelContainer/MarginContainer/VBoxContainer" unique_id=419185334] +custom_minimum_size = Vector2(0, 6.7) +layout_mode = 2 + +[node name="HBoxContainer2" type="HBoxContainer" parent="ResultRoot/CenterContainer/PanelContainer/MarginContainer/VBoxContainer" unique_id=1952087552] +layout_mode = 2 +theme_override_constants/separation = 145 +alignment = 1 + +[node name="HomeButton" type="Button" parent="ResultRoot/CenterContainer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer2" unique_id=882486403] +custom_minimum_size = Vector2(150, 40) +layout_mode = 2 +size_flags_horizontal = 4 +theme_override_fonts/font = ExtResource("3_5u2eh") +theme_override_font_sizes/font_size = 26 +theme_override_styles/normal = SubResource("StyleBoxFlat_qk7ap") +theme_override_styles/pressed = SubResource("StyleBoxFlat_o517f") +theme_override_styles/hover = SubResource("StyleBoxFlat_ndexe") +text = "Home" + +[node name="ExitButton" type="Button" parent="ResultRoot/CenterContainer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer2" unique_id=2007003856] +custom_minimum_size = Vector2(150, 40) +layout_mode = 2 +size_flags_horizontal = 4 +theme_override_fonts/font = ExtResource("3_5u2eh") +theme_override_font_sizes/font_size = 26 +theme_override_styles/normal = SubResource("StyleBoxFlat_qk7ap") +theme_override_styles/pressed = SubResource("StyleBoxFlat_o517f") +theme_override_styles/hover = SubResource("StyleBoxFlat_ndexe") +text = "Exit" + +[node name="HealthIconsA" type="HBoxContainer" parent="ResultRoot" unique_id=970283826] +unique_name_in_owner = true +layout_mode = 0 +offset_left = 300.0 +offset_top = 350.0 +offset_right = 300.0 +offset_bottom = 350.0 +pivot_offset_ratio = Vector2(0.5, 0.5) +mouse_filter = 2 +alignment = 1 +script = ExtResource("4_6ykjf") +unit_icon = ExtResource("5_56pta") +icon_size = Vector2(30, 30) + +[node name="HealthIconsB" type="HBoxContainer" parent="ResultRoot" unique_id=338515395] +unique_name_in_owner = true +layout_mode = 0 +offset_left = 300.0 +offset_top = 350.0 +offset_right = 300.0 +offset_bottom = 350.0 +pivot_offset_ratio = Vector2(0.5, 0.5) +mouse_filter = 2 +alignment = 1 +script = ExtResource("4_6ykjf") +unit_icon = ExtResource("5_56pta") +icon_size = Vector2(30, 30) + +[connection signal="button_up" from="ResultRoot/CenterContainer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer2/HomeButton" to="." method="_on_home_button_button_up"] +[connection signal="button_up" from="ResultRoot/CenterContainer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer2/ExitButton" to="." method="_on_exit_button_button_up"] diff --git a/Scripts/demo_renderer.gd b/Scripts/demo_renderer.gd index d2614908..68dabb90 100644 --- a/Scripts/demo_renderer.gd +++ b/Scripts/demo_renderer.gd @@ -21,6 +21,7 @@ const FALLBACK_HEALTH_TEXTURE = preload("res://Shapes/feather.svg") @export var high_stage_b: Node2D @export var camera_a: Camera2D @export var camera_b: Camera2D +@export var result_screen: CanvasLayer var _ghosts_a: Dictionary = {} var _ghosts_b: Dictionary = {} @@ -54,6 +55,11 @@ func _ready() -> void: if nm.has_signal("connection_succeeded"): nm.connection_succeeded.connect(_identify_as_demo.bind(nm)) + if nm.has_signal("multiplayer_finish"): + nm.multiplayer_finish.connect(_on_multiplayer_finish) + if not result_screen: + result_screen = get_node_or_null("../DemoResultScreen") + # Draw arena walls on both stages # Use get_node as fallback — exported NodePath may fail across SubViewport boundaries if not stage_a: @@ -134,9 +140,12 @@ func _apply_screen(screen: Dictionary, screen_data: Dictionary) -> void: return var ghosts: Dictionary = screen["ghosts"] + screen["peer_id"] = screen_data.get("peer_id", -1) + var player_state: Dictionary = screen_data.get("player", {}) + screen["last_player_state"] = player_state + Global.high_stage = screen.get("high_stage", stage) - var player_state: Dictionary = screen_data.get("player", {}) _apply_player(screen, stage, player_state) # Apply trap ghosts @@ -540,3 +549,31 @@ func _create_player_ghost(stage: Node2D) -> Node2D: ghost.modulate = Color(0.2, 0.6, 1.0, 1.0) stage.add_child(ghost) return ghost + + +func _on_multiplayer_finish(winner_peer_ids: Array, draw: bool, forfeit: bool) -> void: + var nm := get_node_or_null("/root/NetworkManager") + if nm and nm.demo_state_received.is_connected(_on_demo_state_received): + nm.demo_state_received.disconnect(_on_demo_state_received) + + get_tree().paused = true + + if result_screen: + var state_a: Dictionary = _screens[0].get("last_player_state", {}) + var state_b: Dictionary = _screens[1].get("last_player_state", {}) + var peer_id_a: int = _screens[0].get("peer_id", -1) + var peer_id_b: int = _screens[1].get("peer_id", -1) + + # 獲取 HUD 上的玩家名稱,若無則預設為 Player A / Player B + var name_a := "Player A (Left)" + var name_b := "Player B (Right)" + var name_label_a = get_node_or_null("../HUD/NameLabelA") + if name_label_a and name_label_a.text != "": + name_a = name_label_a.text + " (Left)" + var name_label_b = get_node_or_null("../HUD/NameLabelB") + if name_label_b and name_label_b.text != "": + name_b = name_label_b.text + " (Right)" + + result_screen.show_demo_results( + state_a, state_b, winner_peer_ids, draw, forfeit, peer_id_a, peer_id_b, name_a, name_b + ) diff --git a/Scripts/demo_result_screen.gd b/Scripts/demo_result_screen.gd new file mode 100644 index 00000000..c744a444 --- /dev/null +++ b/Scripts/demo_result_screen.gd @@ -0,0 +1,215 @@ +class_name DemoResultScreen +extends CanvasLayer + +var fade_tween: Tween + +@onready var stat_names_label: Label = %StatNamesLabel +@onready var stat_values_label: Label = %StatValuesALabel +@onready var stat_values_b_label: Label = %StatValuesBLabel +@onready var health_icon = %HealthIconsA +@onready var health_icon_b = %HealthIconsB +@onready var vboxcontainer = %VBoxContainer +@onready var result_root: Control = $ResultRoot +@onready var title_label: Label = %TitleLabel +@onready var border_panel: PanelContainer = $"ResultRoot/CenterContainer/PanelContainer" + + +func _ready() -> void: + hide() + + +func _input(event: InputEvent) -> void: + if event is InputEventKey and not event.pressed: + if event.keycode == KEY_ESCAPE or event.keycode == KEY_ENTER: + if visible: + _on_home_button_button_up() + get_viewport().set_input_as_handled() + + +func show_demo_results( + results_a: Dictionary, + results_b: Dictionary, + winner_peer_ids: Array, + draw: bool, + forfeit: bool, + peer_id_a: int, + peer_id_b: int, + name_a: String = "Player A (Left)", + name_b: String = "Player B (Right)" +) -> void: + var survival_seconds_a := int(results_a.get("elapsed_time", 0.0)) + var survival_seconds_b := int(results_b.get("elapsed_time", 0.0)) + + # 1. 解決死亡/輸家血量顯示 1 的問題 + # 若非平手,且玩家未贏得比賽,則其剩餘血量強制顯示為 0 + var health_val_a = int(results_a.get("health", 0)) + var health_val_b = int(results_b.get("health", 0)) + if not draw: + if not peer_id_a in winner_peer_ids: + health_val_a = 0 + if not peer_id_b in winner_peer_ids: + health_val_b = 0 + + # "Remaining Health" 行設定為空字串 "",因為血量 Icon 會動態對齊覆蓋在此行 + var names: Array[String] = [ + "", + "Survival Time", + "Remaining Health", + "Energy Balls Collected", + "Jumps", + "Distance Traveled", + "Total Energy Spent", + ] + + # 使用傳入的真實名稱 + var values_a: Array[String] = [ + name_a, + "%02d:%02d" % [survival_seconds_a / 60, survival_seconds_a % 60], + "", + str(int(results_a.get("energy_ball_count", 0))), + str(int(results_a.get("jump_count", 0))), + "%.1f" % float(results_a.get("distance_traveled", 0.0)), + str(int(results_a.get("energy_spent", 0))), + ] + + var values_b: Array[String] = [ + name_b, + "%02d:%02d" % [survival_seconds_b / 60, survival_seconds_b % 60], + "", + str(int(results_b.get("energy_ball_count", 0))), + str(int(results_b.get("jump_count", 0))), + "%.1f" % float(results_b.get("distance_traveled", 0.0)), + str(int(results_b.get("energy_spent", 0))), + ] + + # 設定勝負標題與顏色 + var win_color := Color.LAWN_GREEN + var draw_color := Color.LIGHT_SKY_BLUE + + # 移除 " (Left)" 或 " (Right)" 後綴以在勝負大字中使用實際名字 + var display_name_a := name_a.trim_suffix(" (Left)") + var display_name_b := name_b.trim_suffix(" (Right)") + + if draw: + title_label.text = "DRAW" + title_label.modulate = draw_color + else: + var a_won := peer_id_a in winner_peer_ids + var b_won := peer_id_b in winner_peer_ids + if a_won and b_won: + title_label.text = "DRAW" + title_label.modulate = draw_color + elif a_won: + if forfeit: + title_label.text = "%s WINS (by forfeit)" % display_name_a.to_upper() + else: + title_label.text = "%s WINS" % display_name_a.to_upper() + title_label.modulate = win_color + elif b_won: + if forfeit: + title_label.text = "%s WINS (by forfeit)" % display_name_b.to_upper() + else: + title_label.text = "%s WINS" % display_name_b.to_upper() + title_label.modulate = win_color + else: + title_label.text = "NO WINNER" + title_label.modulate = draw_color + + var panel_style_box = StyleBoxFlat.new() + panel_style_box.border_color = title_label.modulate + panel_style_box.bg_color = Color(0, 0, 0, 0) + panel_style_box.set_border_width_all(3) + panel_style_box.set_corner_radius_all(27) + border_panel.add_theme_stylebox_override("panel", panel_style_box) + + stat_names_label.text = "\n".join(names) + stat_values_label.text = "\n".join(values_a) + stat_values_b_label.text = "\n".join(values_b) + + # 設定 Player A 血量 Icon + health_icon.modulate.a = 0.0 + health_icon.reversed = 1 + health_icon.set_icon_size(Vector2(25, 25)) + health_icon._ensure_icons() + health_icon.set_health(health_val_a) + + # 設定 Player B 血量 Icon + health_icon_b.modulate.a = 0.0 + health_icon_b.reversed = 1 + health_icon_b.set_icon_size(Vector2(25, 25)) + health_icon_b._ensure_icons() + health_icon_b.set_health(health_val_b) + + result_root.modulate.a = 0.0 + show() + result_root.show() + health_icon.show() + health_icon_b.show() + health_icon.queue_sort() + health_icon_b.queue_sort() + vboxcontainer.queue_sort() + + # 等待排版布局完成 + await get_tree().process_frame + await get_tree().process_frame + + # 對齊雙方的血量 Icon + _align_health_icon_to_column(health_icon, stat_values_label) + _align_health_icon_to_column(health_icon_b, stat_values_b_label) + health_icon.modulate.a = 1.0 + health_icon_b.modulate.a = 1.0 + + Audio.set_bgm(Audio.BGM.RESULT_SCREEN) + _fade_in(3.0) + + +# 通用的血量 Icon 對齊方法 +func _align_health_icon_to_column(icon_node: HBoxContainer, values_label: Label) -> void: + var character_index := stat_names_label.text.find("Remaining Health") + if character_index == -1: + push_warning("Cannot find Remaining Health in StatNamesLabel") + return + var character_rect := stat_names_label.get_character_bounds(character_index) + if character_rect.size == Vector2.ZERO: + push_warning("Remaining Health text has not been laid out yet") + return + + var character_center_local := character_rect.get_center() + var character_center_global := stat_names_label.get_global_transform() * character_center_local + var values_right_global := ( + values_label.get_global_transform() * Vector2(values_label.size.x, 0.0) + ) + var icon_transform: Transform2D = icon_node.get_global_transform() + var icon_right_center_global: Vector2 = ( + icon_transform * Vector2(icon_node.size.x, icon_node.size.y * 0.5) + ) + var target_global := Vector2(values_right_global.x, character_center_global.y) + icon_node.global_position += (target_global - icon_right_center_global) + + +func _fade_in(duration: float = 1.5) -> void: + show() + if fade_tween != null and fade_tween.is_valid(): + fade_tween.kill() + fade_tween = create_tween() + fade_tween.set_pause_mode(Tween.TWEEN_PAUSE_PROCESS) + ( + fade_tween + . tween_property(result_root, "modulate:a", 1.0, duration) + . set_trans(Tween.TRANS_EXPO) + . set_ease(Tween.EASE_OUT) + ) + + +func _on_home_button_button_up() -> void: + var player: AudioStreamPlayer = Audio.play_sfx(Audio.SFX.BUTTON_PRESS) + SceneTransition.transition_to("res://Scenes/menu.tscn") + await get_tree().create_timer(2).timeout + hide() + get_tree().paused = false + + +func _on_exit_button_button_up() -> void: + var player: AudioStreamPlayer = Audio.play_sfx(Audio.SFX.BUTTON_PRESS) + await get_tree().create_timer(0.5).timeout + get_tree().quit() diff --git a/Scripts/demo_result_screen.gd.uid b/Scripts/demo_result_screen.gd.uid new file mode 100644 index 00000000..837a2f79 --- /dev/null +++ b/Scripts/demo_result_screen.gd.uid @@ -0,0 +1 @@ +uid://dltobunw2epqg diff --git a/Scripts/network/state_serializer.gd b/Scripts/network/state_serializer.gd index 03e55901..4dffc0ff 100644 --- a/Scripts/network/state_serializer.gd +++ b/Scripts/network/state_serializer.gd @@ -78,6 +78,7 @@ func _serialize_player(p: Node) -> Dictionary: var energy := 0 var ball_count := 0 + var total_energy_spent := 0 if game_manager: energy = game_manager.get("energy_amount") if "energy_amount" in game_manager else 0 ball_count = ( @@ -95,6 +96,10 @@ func _serialize_player(p: Node) -> Dictionary: "energy": energy, "energy_ball_count": ball_count, "skin_id": p.get("skin_id") if "skin_id" in p else "", + "jump_count": p.get("jump_count") if "jump_count" in p else 0, + "distance_traveled": p.get("distance_traveled") if "distance_traveled" in p else 0.0, + "energy_spent": total_energy_spent, + "elapsed_time": game_manager.get("elapsed_time") if game_manager else 0.0, } From 81676d11125882a39b11821fa7be679e9832724e Mon Sep 17 00:00:00 2001 From: prairie2022 Date: Sat, 18 Jul 2026 04:19:38 +0800 Subject: [PATCH 2/2] feat: move survival time to title In `demo_result_screen`, move survival time to title as ` at