feat: Overhaul of the Game Over UI, custom texture integrations, and fixing visual artifacts.
This commit is contained in:
@@ -14,6 +14,7 @@ var active_safe_zone_rects: Array[Rect2i] = []
|
||||
var spawned_safe_zones: int = 0
|
||||
var _safe_zone_animating: bool = false
|
||||
var _outline_nodes: Array[Node3D] = [] # Track perimeter outline containers for cleanup
|
||||
var _cached_safe_zone_mesh: Mesh = null
|
||||
|
||||
# Power-Up Tile Spawning
|
||||
const POWERUP_TILES = [11, 14] # Speed, Ghost (Freeze and Wall excluded in this mode)
|
||||
@@ -654,7 +655,10 @@ func _animate_safe_zone_appear():
|
||||
_safe_zone_animating = false
|
||||
return
|
||||
|
||||
var original_mesh = gridmap.mesh_library.get_item_mesh(TILE_SAFE)
|
||||
if not _cached_safe_zone_mesh:
|
||||
_cached_safe_zone_mesh = gridmap.mesh_library.get_item_mesh(TILE_SAFE)
|
||||
|
||||
var original_mesh = _cached_safe_zone_mesh
|
||||
if not is_instance_valid(original_mesh):
|
||||
_safe_zone_animating = false
|
||||
return
|
||||
@@ -854,12 +858,10 @@ func _animate_safe_zone_disappear():
|
||||
var cur_mat = cur_mesh.material
|
||||
if not is_instance_valid(cur_mat): return
|
||||
|
||||
var fade_mat: StandardMaterial3D = cur_mat.duplicate()
|
||||
var fade_mesh = cur_mesh.duplicate()
|
||||
fade_mesh.material = fade_mat
|
||||
gridmap.mesh_library.set_item_mesh(TILE_SAFE, fade_mesh)
|
||||
# Instead of continuously duplicating, use the current duplicated material for tweening.
|
||||
var fade_mat: StandardMaterial3D = cur_mat
|
||||
|
||||
var start_alpha: float = cur_mat.albedo_color.a
|
||||
var start_alpha: float = fade_mat.albedo_color.a
|
||||
var tween = create_tween()
|
||||
|
||||
# Phase 1 — Flicker brighten: alpha → 0.95 in 0.15s (warn the player)
|
||||
@@ -895,6 +897,17 @@ func _animate_safe_zone_disappear():
|
||||
func(a: float): if is_instance_valid(omat): omat.albedo_color.a = a,
|
||||
1.0, 0.0, 0.4
|
||||
).set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_EXPO)
|
||||
|
||||
# Wait for animation to finish, then free outlines locally
|
||||
await get_tree().create_timer(0.6).timeout
|
||||
for outline in _outline_nodes:
|
||||
if is_instance_valid(outline):
|
||||
outline.queue_free()
|
||||
_outline_nodes.clear()
|
||||
|
||||
# Restore the original safe zone mesh to prevent material/alpha leakage
|
||||
if _cached_safe_zone_mesh and gridmap and gridmap.mesh_library:
|
||||
gridmap.mesh_library.set_item_mesh(TILE_SAFE, _cached_safe_zone_mesh)
|
||||
|
||||
func _clear_dynamic_safe_zones():
|
||||
var gridmap = get_parent().get_node_or_null("EnhancedGridMap")
|
||||
@@ -926,11 +939,8 @@ func _clear_dynamic_safe_zones():
|
||||
active_safe_zone_rects.clear()
|
||||
spawned_safe_zones = 0
|
||||
|
||||
# Free all outline containers now that the animation has finished
|
||||
for outline in _outline_nodes:
|
||||
if is_instance_valid(outline):
|
||||
outline.queue_free()
|
||||
_outline_nodes.clear()
|
||||
# Outline deletion and mesh restoration is now handled locally by each client
|
||||
# at the end of _animate_safe_zone_disappear()
|
||||
|
||||
func _scatter_player_tiles(player_node: Node):
|
||||
"""Server: Take all tiles from player's playerboard and scatter them onto nearby grid cells."""
|
||||
|
||||
+22
-20
@@ -194,17 +194,21 @@ func _process(delta):
|
||||
rotation = carrier.rotation
|
||||
|
||||
_update_prompt_label()
|
||||
|
||||
if prompt_container and prompt_container.visible:
|
||||
var time = Time.get_ticks_msec() / 1000.0
|
||||
prompt_container.position.y = 2.4 + sin(time * 5.0) * 0.15
|
||||
|
||||
var mesh_cache: Array[MeshInstance3D] = []
|
||||
var original_scales: Array[Vector3] = []
|
||||
var prompt_label: Label3D
|
||||
var prompt_container: Node3D
|
||||
@onready var SettingsManager = get_node_or_null("/root/SettingsManager")
|
||||
|
||||
func _update_prompt_label():
|
||||
if not prompt_label: return
|
||||
if not prompt_container: return
|
||||
|
||||
if is_static_turret or is_carried or is_thrown or is_recovering:
|
||||
prompt_label.visible = false
|
||||
prompt_container.visible = false
|
||||
return
|
||||
|
||||
var authority_player = null
|
||||
@@ -215,40 +219,38 @@ func _update_prompt_label():
|
||||
break
|
||||
|
||||
if not authority_player:
|
||||
prompt_label.visible = false
|
||||
prompt_container.visible = false
|
||||
return
|
||||
|
||||
# Check distance
|
||||
var player_pos = Vector2(authority_player.current_position.x, authority_player.current_position.y)
|
||||
var tekton_pos = Vector2(current_position.x, current_position.y)
|
||||
if player_pos.distance_to(tekton_pos) > 1.5:
|
||||
prompt_label.visible = false
|
||||
prompt_container.visible = false
|
||||
return
|
||||
|
||||
# Check power bar
|
||||
var pw_mgr = authority_player.get_node_or_null("PowerUpManager")
|
||||
if pw_mgr and pw_mgr.current_boost >= (pw_mgr.MAX_BOOST - 1):
|
||||
prompt_label.visible = true
|
||||
prompt_container.visible = true
|
||||
else:
|
||||
prompt_label.visible = false
|
||||
prompt_container.visible = false
|
||||
|
||||
func _ready():
|
||||
# Cache meshes and their initial scales
|
||||
_cache_meshes(self)
|
||||
|
||||
prompt_label = Label3D.new()
|
||||
var shortcut_text = "G"
|
||||
if SettingsManager and SettingsManager.has_method("get_control_text"):
|
||||
shortcut_text = SettingsManager.get_control_text("tekton_grab")
|
||||
prompt_label.text = "[ " + str(shortcut_text) + " ]"
|
||||
prompt_label.font_size = 64
|
||||
prompt_label.outline_size = 12
|
||||
prompt_label.billboard = BaseMaterial3D.BILLBOARD_ENABLED
|
||||
prompt_label.no_depth_test = true
|
||||
prompt_label.position = Vector3(0, 1.8, 0)
|
||||
prompt_label.modulate = Color(1.0, 0.9, 0.0) # Yellow text
|
||||
prompt_label.visible = false
|
||||
add_child(prompt_label)
|
||||
prompt_container = get_node_or_null("InteractionPrompt")
|
||||
if prompt_container:
|
||||
prompt_container.visible = false
|
||||
var key_label = prompt_container.get_node_or_null("KeyLabel")
|
||||
if key_label:
|
||||
var shortcut_text = "G"
|
||||
if SettingsManager and SettingsManager.has_method("get_control_text"):
|
||||
shortcut_text = SettingsManager.get_control_text("tekton_grab")
|
||||
key_label.text = "[ " + str(shortcut_text) + " ]"
|
||||
else:
|
||||
push_warning("[Tekton] 'InteractionPrompt' node missing. UI will not appear.")
|
||||
|
||||
func _cache_meshes(node: Node):
|
||||
if node is MeshInstance3D:
|
||||
|
||||
@@ -19,7 +19,10 @@ signal rematch_pressed
|
||||
@onready var player_score_label := %PlayerScoreLabel as Label
|
||||
@onready var completion_value := %CompletionValue as Label
|
||||
@onready var score_value := %ScoreValue as Label
|
||||
@onready var rank_value := %RankValue as Label
|
||||
@onready var rank_value := %RankValue as Panel
|
||||
|
||||
# Content Panel
|
||||
@onready var content_panel := %ContentPanel as PanelContainer
|
||||
|
||||
# Rank List tab
|
||||
@onready var rank_list_content := %RankListContent as VBoxContainer
|
||||
@@ -87,7 +90,48 @@ func _ready() -> void:
|
||||
func _switch_tab(show_race_result: bool) -> void:
|
||||
race_result_content.visible = show_race_result
|
||||
rank_list_content.visible = not show_race_result
|
||||
tab_title_label.text = "RACE RESULT" if show_race_result else "RANK LIST"
|
||||
if tab_title_label:
|
||||
tab_title_label.text = "RACE RESULT" if show_race_result else "RANK LIST"
|
||||
|
||||
# Tab button styles
|
||||
var tex_avail_normal = preload("res://assets/graphics/gui/game_over_panel/Button_avail.png")
|
||||
var tex_avail_click = preload("res://assets/graphics/gui/game_over_panel/Button_avail_click.png")
|
||||
var tex_avail_hover = preload("res://assets/graphics/gui/game_over_panel/Button_avail_expand.png")
|
||||
var tex_unavail_normal = preload("res://assets/graphics/gui/game_over_panel/Button_unavail.png")
|
||||
var tex_unavail_click = preload("res://assets/graphics/gui/game_over_panel/Button_unavail_click.png")
|
||||
|
||||
if show_race_result:
|
||||
race_result_tab_btn.add_theme_stylebox_override("normal", _get_stylebox(tex_avail_normal))
|
||||
race_result_tab_btn.add_theme_stylebox_override("pressed", _get_stylebox(tex_avail_click))
|
||||
race_result_tab_btn.add_theme_stylebox_override("hover", _get_stylebox(tex_avail_hover))
|
||||
|
||||
rank_list_tab_btn.add_theme_stylebox_override("normal", _get_stylebox(tex_unavail_normal))
|
||||
rank_list_tab_btn.add_theme_stylebox_override("pressed", _get_stylebox(tex_unavail_click))
|
||||
rank_list_tab_btn.add_theme_stylebox_override("hover", _get_stylebox(tex_unavail_normal))
|
||||
else:
|
||||
race_result_tab_btn.add_theme_stylebox_override("normal", _get_stylebox(tex_unavail_normal))
|
||||
race_result_tab_btn.add_theme_stylebox_override("pressed", _get_stylebox(tex_unavail_click))
|
||||
race_result_tab_btn.add_theme_stylebox_override("hover", _get_stylebox(tex_unavail_normal))
|
||||
|
||||
rank_list_tab_btn.add_theme_stylebox_override("normal", _get_stylebox(tex_avail_normal))
|
||||
rank_list_tab_btn.add_theme_stylebox_override("pressed", _get_stylebox(tex_avail_click))
|
||||
rank_list_tab_btn.add_theme_stylebox_override("hover", _get_stylebox(tex_avail_hover))
|
||||
|
||||
# Content Panel Background
|
||||
var bg_tex = preload("res://assets/graphics/gui/game_over_panel/raceresult.png") if show_race_result else preload("res://assets/graphics/gui/game_over_panel/ranklist.png")
|
||||
var bg_style = StyleBoxTexture.new()
|
||||
bg_style.texture = bg_tex
|
||||
# Must match margins from tscn: left 50, top 180, right 80, bottom 20
|
||||
bg_style.content_margin_left = 50
|
||||
bg_style.content_margin_top = 180
|
||||
bg_style.content_margin_right = 80
|
||||
bg_style.content_margin_bottom = 20
|
||||
content_panel.add_theme_stylebox_override("panel", bg_style)
|
||||
|
||||
func _get_stylebox(tex: Texture2D) -> StyleBoxTexture:
|
||||
var sb = StyleBoxTexture.new()
|
||||
sb.texture = tex
|
||||
return sb
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Data Population (called by main.gd)
|
||||
@@ -119,14 +163,21 @@ func setup(player_scores: Array, local_peer_id: int) -> void:
|
||||
player_score_label.text = str(local_score)
|
||||
completion_value.text = "%dx" % local_goal_count
|
||||
score_value.text = str(local_score)
|
||||
rank_value.text = _get_ordinal(local_rank + 1)
|
||||
|
||||
# Update RankValue Panel texture based on rank
|
||||
var rank_str = _get_ordinal(local_rank + 1)
|
||||
var rank_tex_path = "res://assets/graphics/gui/game_over_panel/%s.png" % rank_str
|
||||
if ResourceLoader.exists(rank_tex_path):
|
||||
var rank_tex = load(rank_tex_path)
|
||||
var rank_style = StyleBoxTexture.new()
|
||||
rank_style.texture = rank_tex
|
||||
rank_value.add_theme_stylebox_override("panel", rank_style)
|
||||
|
||||
# Rank color
|
||||
var rank_color := Color(0.95, 0.75, 0.1) # Gold
|
||||
if local_rank == 1: rank_color = Color(0.75, 0.75, 0.75) # Silver
|
||||
elif local_rank == 2: rank_color = Color(0.8, 0.5, 0.2) # Bronze
|
||||
elif local_rank > 2: rank_color = Color(0.6, 0.6, 0.6)
|
||||
rank_value.add_theme_color_override("font_color", rank_color)
|
||||
player_rank_label.add_theme_color_override("font_color", rank_color)
|
||||
|
||||
# --- Rank List tab ---
|
||||
@@ -199,6 +250,13 @@ func _setup_3d_preview() -> void:
|
||||
if not character_root:
|
||||
return
|
||||
_anim_player = character_root.get_node_or_null("AnimationPlayer")
|
||||
_set_layers_recursive(character_root, 512)
|
||||
|
||||
func _set_layers_recursive(node: Node, layer_mask: int) -> void:
|
||||
if node is VisualInstance3D:
|
||||
node.layers = layer_mask
|
||||
for child in node.get_children():
|
||||
_set_layers_recursive(child, layer_mask)
|
||||
|
||||
func _update_3d_preview(character_name: String) -> void:
|
||||
if not character_root:
|
||||
|
||||
Reference in New Issue
Block a user