feat: Add PlayerboardManager, SpecialTilesManager, PowerupInventoryUI, and new touch control/power tile assets.

This commit is contained in:
Yogi Wiguna
2026-02-02 18:01:42 +08:00
parent 614d678b84
commit 753757d273
20 changed files with 558 additions and 198 deletions
+88 -11
View File
@@ -8,6 +8,7 @@ var selection_indicators: Dictionary = {} # { EffectEnum: Control }
# Local State
var selected_effect: int = -1
var special_manager_ref: Node = null # Reference to SpecialTilesManager
signal effect_selected(effect: int)
@@ -22,14 +23,19 @@ func _ready():
print("PowerUpUI: Container not found")
return
# SpecialEffect.BURN_TILES (0) -> Coin
_setup_icon(0, container.get_node_or_null("CoinIcon"))
# SpecialEffect.BLOCK_FLOOR (3) -> Heart
_setup_icon(3, container.get_node_or_null("HeartIcon"))
# SpecialEffect.FREEZE_PLAYER (2) -> Diamond
_setup_icon(2, container.get_node_or_null("DiamondIcon"))
# SpecialEffect.INVISIBLE_MODE (4) -> Star
_setup_icon(4, container.get_node_or_null("StarIcon"))
# ID 11 = Faster (Coin Icon?) User said: "Coin : random between two" originally,
# but now "11 (Faster)". Let's use CoinIcon for Speed? Or Star?
# User Request: "CoinIcon, HeartIcon, DiamondIcon and StarIcon"
# Let's map:
# 11 (Faster) -> CoinIcon
# 12 (Freeze) -> DiamondIcon
# 13 (Block) -> HeartIcon
# 14 (Invisible) -> StarIcon
_setup_icon(11, container.get_node_or_null("CoinIcon"))
_setup_icon(12, container.get_node_or_null("DiamondIcon"))
_setup_icon(13, container.get_node_or_null("HeartIcon"))
_setup_icon(14, container.get_node_or_null("StarIcon"))
# Note: SpecialEffect enum values from SpecialTilesManager:
# BURN_TILES = 0
@@ -50,24 +56,95 @@ func _setup_icon(effect_id: int, node: Control):
selection_indicators[effect_id] = select_rect
select_rect.visible = false
# Add Cooldown Label if missing
if not node.has_node("CooldownLabel"):
var lbl = Label.new()
lbl.name = "CooldownLabel"
lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE # Ensure input passes to icon
# Style the label
lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
lbl.set_anchors_preset(Control.PRESET_FULL_RECT) # Cover icon
lbl.add_theme_color_override("font_shadow_color", Color.BLACK)
lbl.add_theme_constant_override("shadow_offset_x", 1)
lbl.add_theme_constant_override("shadow_offset_y", 1)
lbl.add_theme_font_size_override("font_size", 20) # Big text
lbl.text = ""
lbl.hide()
node.add_child(lbl)
# Connect click event
if not node.gui_input.is_connected(_on_icon_input):
node.gui_input.connect(_on_icon_input.bind(effect_id))
func _on_icon_input(event, effect_id: int):
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
if selected_effect == effect_id:
deselect()
print("[PowerUpUI] Clicked Icon %d. Manager: %s" % [effect_id, special_manager_ref])
# User Request: "Click the CoinIcon for activate"
# Instead of just selecting, we ACTIVATE it immediately.
if special_manager_ref:
special_manager_ref.activate_effect(effect_id)
else:
select_effect(effect_id)
print("[PowerUpUI] ERROR: SpecialManagerRef is null!")
# Visual feedback (still useful)
select_effect(effect_id)
# Auto-deselect after short delay?
# For now, keep selection highlight as feedback of "last used" or "active"?
# Or just flash it?
# If it's a "selection for target" ability (like Block Floor on Target?),
# we might need selection state first, then clicking target.
# But user said "CoinIcon ... activate faster speed". Speed is self-cast.
# Freeze is 3x3 self-centered.
# Invisible is self.
# Block is "Wall Block"... maybe checks target?
# SpecialTilesManager.activate_effect defaults target to self if null.
# So direct activation is safe for IsInstant effects.
func setup(player_node):
var special_manager = player_node.get_node_or_null("SpecialTilesManager")
if special_manager:
special_manager_ref = special_manager # Store reference for activation logic
if not special_manager.is_connected("inventory_updated", _on_inventory_updated):
special_manager.connect("inventory_updated", _on_inventory_updated)
if not special_manager.is_connected("cooldown_updated", _on_cooldown_updated):
special_manager.connect("cooldown_updated", _on_cooldown_updated)
_on_inventory_updated(special_manager.inventory)
func _on_cooldown_updated(effect: int, time_left: float, max_time: float):
if icon_containers.has(effect):
var node = icon_containers[effect]
var lbl = node.get_node_or_null("CooldownLabel")
if lbl:
if time_left > 0:
lbl.text = "%.1fs" % time_left
lbl.show()
node.modulate = Color(0.5, 0.5, 0.5, 1.0) # Dimmed
else:
lbl.hide()
# Check inventory to restore proper modulate
var has_item = false # Need ref to inventory... or rely on next inventory update?
# Inventory update might not fire when cooldown ends.
# Let's restore bright if we have it?
# We don't have inventory reference here easily without storing it.
node.modulate = Color.WHITE # Assume we have it if we used it?
# Or better, just restore to WHITE and let inventory logic handle "not owned" graying.
# But wait, logic in _on_inventory_updated sets GRAY if not owned.
# If we set WHITE here, we might un-gray an unowned item?
# The only way cooldown runs is if we ACTIVATED it, so we HAD it.
# And we treat it as infinite consumable now. So we still have it.
pass
# Better modulate handling:
# If cooldown > 0, DIM.
# If cooldown == 0, check owned state?
# For now, simplistic: if cooldown ends, set white.
if time_left <= 0:
node.modulate = Color.WHITE
func _on_inventory_updated(inventory: Dictionary):
# Update UI icons (Dimmed vs Lit)
for effect in icon_containers: