feat: skin update

This commit is contained in:
2026-04-20 19:32:52 +08:00
parent b492dc99b6
commit f2e14f20f3
28 changed files with 1396 additions and 113 deletions
+67 -30
View File
@@ -27,7 +27,22 @@ signal closed
# --- State ---
var current_category: String = "head"
var current_char_idx: int = 0
# Node names inside the GLB scene (CharacterRoot children)
var available_chars: Array[String] = ["Bob", "Masbro", "Gatot", "Oldpop"]
# Display name shown in the label -> node name in GLB
const DISPLAY_TO_NODE: Dictionary = {
"Copper": "Oldpop",
"Dabro": "Masbro",
"Pip": "Bob",
"Gatot": "Gatot",
}
# Reverse: node name -> display name shown to player
const NODE_TO_DISPLAY: Dictionary = {
"Oldpop": "Copper",
"Masbro": "Dabro",
"Bob": "Pip",
"Gatot": "Gatot",
}
# Drag tracking
var _is_dragging: bool = false
@@ -81,12 +96,13 @@ func _ready() -> void:
# Local 3D preview
# -----------------------------------------------------------------------
func _setup_3d_preview() -> void:
# Attempt to match the user's currently saved loadout character
var def_char: String = UserProfileManager.profile.get("loadout_character", "Bob")
var idx = available_chars.find(def_char)
# loadout_character stores display names (e.g. "Copper") — convert to node name first
var def_raw: String = UserProfileManager.profile.get("loadout_character", "")
var def_node: String = DISPLAY_TO_NODE.get(def_raw, def_raw) # "Copper" -> "Oldpop"
var idx: int = available_chars.find(def_node)
if idx != -1:
current_char_idx = idx
_update_char_name_label()
_update_preview_char()
@@ -101,31 +117,30 @@ func _on_next_char() -> void:
_update_preview_char()
func _update_char_name_label() -> void:
char_name_label.text = available_chars[current_char_idx]
var node_name: String = available_chars[current_char_idx]
# Show the player-facing display name (e.g. "Copper" instead of "Oldpop")
char_name_label.text = NODE_TO_DISPLAY.get(node_name, node_name)
func _update_preview_char() -> void:
if not character_root: return
var target_node_name = available_chars[current_char_idx]
var active_char_node: Node3D = null
var target_node_name := available_chars[current_char_idx]
for child in character_root.get_children():
if child is Node3D:
child.visible = (child.name == target_node_name)
if child.name == target_node_name:
active_char_node = child
var active_char_node := character_root.get_node_or_null(target_node_name) as Node3D
if active_char_node and anim_player:
anim_player.root_node = active_char_node.get_path()
if anim_player.has_animation("animation-pack/idle"):
anim_player.play("animation-pack/idle")
elif anim_player.get_animation_list().size() > 0:
anim_player.play(anim_player.get_animation_list()[0])
if active_char_node:
var p = preload("res://scenes/player.gd").new()
p.apply_loadout(active_char_node)
p.free()
# Apply the player's current loadout materials
SkinManager.apply_loadout(character_root, UserProfileManager.loadout)
# -----------------------------------------------------------------------
# Drag-to-rotate
@@ -236,9 +251,12 @@ func _make_star_card(pack: Dictionary) -> Control:
func _make_cosmetic_card(item: Dictionary) -> Control:
var card: Control = template_cosmetic_card.duplicate()
card.visible = true
var item_id: String = item.get("id", "")
var already_owned: bool = UserProfileManager.inventory.has(item_id)
var name_lbl: Label = card.find_child("NameLabel", true, false) as Label
if name_lbl: name_lbl.text = item.get("name", item.get("id", "?"))
if name_lbl: name_lbl.text = item.get("name", item_id)
var rarity: String = item.get("rarity", "Common")
var rarity_lbl: Label = card.find_child("RarityLabel", true, false) as Label
@@ -264,10 +282,18 @@ func _make_cosmetic_card(item: Dictionary) -> Control:
if try_btn: try_btn.pressed.connect(_on_try_pressed.bind(item))
var buy_btn: Button = card.find_child("BuyBtn", true, false) as Button
if buy_btn: buy_btn.pressed.connect(_on_buy_cosmetic_pressed.bind(item))
if buy_btn:
if already_owned:
buy_btn.text = "✓ Owned"
buy_btn.disabled = true
# Dim the entire card to signal it's already purchased
card.modulate = Color(0.55, 0.55, 0.55, 0.85)
else:
buy_btn.pressed.connect(_on_buy_cosmetic_pressed.bind(item))
return card
# -----------------------------------------------------------------------
# Wallet refresh
# -----------------------------------------------------------------------
@@ -281,25 +307,27 @@ func _refresh_wallet() -> void:
# -----------------------------------------------------------------------
# Button callbacks
# -----------------------------------------------------------------------
# Tracks a revert callable from SkinManager.preview_skin
var _preview_revert: Callable = Callable()
func _on_try_pressed(item: Dictionary) -> void:
status_label.text = "Previewing: " + item.get("name", item.get("id", "?"))
# Auto-switch character if the catalog item targets a specific one.
# Auto-switch to the character this skin belongs to (if specified)
if item.has("character"):
var char_name: String = item.get("character")
var idx: int = available_chars.find(char_name)
if idx != -1 and current_char_idx != idx:
current_char_idx = idx
_update_char_name_label()
# Inject into loadout temporarily to preview it without saving
var prev: String = UserProfileManager.loadout.get(current_category, "")
UserProfileManager.loadout[current_category] = item.id
_update_preview_char()
# Revert immediately, so jumping to next character drops preview.
UserProfileManager.loadout[current_category] = prev
_update_preview_char()
# Revert any previous preview first
if _preview_revert.is_valid():
_preview_revert.call()
# Live material preview — SkinManager records a revert snapshot automatically
_preview_revert = SkinManager.preview_skin(character_root, item.get("id", ""))
func _on_buy_gold_pressed(pack: Dictionary) -> void:
status_label.text = "Processing purchase..."
@@ -335,7 +363,16 @@ func _on_buy_cosmetic_pressed(item: Dictionary) -> void:
status_label.text = ("Purchased: " + item.get("name", item.id)) if success else "Purchase failed."
if success:
_refresh_wallet()
# Refresh preview to show newly purchased skin's materials
if _preview_revert.is_valid():
_preview_revert.call()
_preview_revert = Callable()
SkinManager.apply_loadout(character_root, UserProfileManager.loadout)
func _on_close() -> void:
# Clean up any open preview when closing the shop
if _preview_revert.is_valid():
_preview_revert.call()
_preview_revert = Callable()
hide()
emit_signal("closed")