feat: Sketchfab-to-game character pipeline, FP/TP animated views, client-auth netcode, audio system
Character pipeline (tools/): - sketchfab_import.py: search/download via Sketchfab Download API with license/attribution tracking (SKETCHFAB_API_TOKEN) - autorig.py: headless Blender auto-rig — fits a Mixamo-named skeleton to unrigged humanoids, binds automatic weights - merge_animations.py: merges the shared Mixamo-skeleton animation library onto any rigged character, strips root motion, canonical names - pipeline.py: one command chains download -> rig -> animate -> register In-game skin system: - SkinManager autoload reads skins.json (auto-written by the pipeline) - SkinnedPlayerModel rewritten: canonical clips with fallback chains, blend times, speed-scaled locomotion, weapon bone attachment - First-person: full animated body for the owner, head hidden via SkeletonModifier3D; third-person: full model for other players - Skin selector in main menu; skin id synced in multiplayer - Fixed GLBLoader crash (GLTFDocument.get_animation_count doesn't exist) Multiplayer sync overhaul: - Movement is now client-authoritative: the owning peer simulates locally (no input round-trip), server keeps health/kills/death - Remote players interpolate synced_position/velocity with extrapolation and snap-on-teleport - Knockback/impulses routed to the simulating peer Audio: - AudioManager autoload: SFX/Weapons/Footsteps/UI/Music buses, pooled 3D players, variation + pitch randomization, auto-registration from assets/sounds Docs: 3D_ASSET_PIPELINE.md rewritten end-to-end, new ASSET_SOURCES.md (non-procedural animation/map/sound sources) and SOUND_DESIGN.md. Verified with debug/spawn_smoke_test.gd (headless: 24/24 checks pass). Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
552338112e
commit
d05477c135
@@ -61,6 +61,9 @@ func _ready() -> void:
|
||||
_add_button(_ui_vbox, "Settings", _on_settings_pressed)
|
||||
_add_button(_ui_vbox, "Exit Game", _on_exit_pressed)
|
||||
|
||||
# Skin selector (skins come from SkinManager: built-ins + skins.json)
|
||||
_build_skin_selector(_ui_vbox)
|
||||
|
||||
# ==========================================
|
||||
# LEVEL SELECTOR PANEL
|
||||
# ==========================================
|
||||
@@ -355,6 +358,34 @@ func _add_button(parent: Container, text: String, callback: Callable) -> void:
|
||||
btn.pressed.connect(callback)
|
||||
parent.add_child(btn)
|
||||
|
||||
func _build_skin_selector(parent: Container) -> void:
|
||||
var skin_mgr = get_node_or_null("/root/SkinManager")
|
||||
if not skin_mgr:
|
||||
return
|
||||
var row = HBoxContainer.new()
|
||||
row.add_theme_constant_override("separation", 12)
|
||||
parent.add_child(row)
|
||||
|
||||
var lbl = Label.new()
|
||||
lbl.text = "Skin"
|
||||
lbl.add_theme_font_size_override("font_size", 24)
|
||||
row.add_child(lbl)
|
||||
|
||||
var opt = OptionButton.new()
|
||||
opt.custom_minimum_size = Vector2(240, 44)
|
||||
var ids: Array = skin_mgr.get_skin_ids()
|
||||
ids.sort()
|
||||
for i in range(ids.size()):
|
||||
var skin = skin_mgr.get_skin(ids[i])
|
||||
opt.add_item(skin.skin_name, i)
|
||||
opt.set_item_metadata(i, ids[i])
|
||||
if ids[i] == skin_mgr.active_skin_id:
|
||||
opt.select(i)
|
||||
opt.item_selected.connect(func(idx: int):
|
||||
skin_mgr.set_active_skin(opt.get_item_metadata(idx))
|
||||
)
|
||||
row.add_child(opt)
|
||||
|
||||
func _on_back_pressed() -> void:
|
||||
_level_selector_panel.hide()
|
||||
_ui_vbox.show()
|
||||
|
||||
Reference in New Issue
Block a user