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]>
51 lines
1.6 KiB
GDScript
51 lines
1.6 KiB
GDScript
extends RefCounted
|
|
class_name GLBLoader
|
|
|
|
## Loads a GLB file at runtime using GLTFDocument.
|
|
## Usage:
|
|
## var loader = GLBLoader.new()
|
|
## var scene = loader.load("res://path/to/model.glb")
|
|
## if scene:
|
|
## get_tree().current_scene.add_child(scene)
|
|
|
|
static func load(glb_path: String) -> Node3D:
|
|
print("GLBLoader: loading " + glb_path)
|
|
|
|
# Check if file exists
|
|
if not FileAccess.file_exists(glb_path):
|
|
print("GLBLoader: file not found: " + glb_path)
|
|
return null
|
|
|
|
# Read file as bytes
|
|
var file = FileAccess.open(glb_path, FileAccess.READ)
|
|
if not file:
|
|
print("GLBLoader: failed to open file")
|
|
return null
|
|
|
|
var bytes = file.get_buffer(file.get_length())
|
|
file.close()
|
|
|
|
print("GLBLoader: read %d bytes" % bytes.size())
|
|
|
|
# Parse with GLTFDocument
|
|
var gltf = GLTFDocument.new()
|
|
var state = GLTFState.new()
|
|
|
|
var err = gltf.append_from_buffer(bytes, "", state)
|
|
if err != OK:
|
|
print("GLBLoader: failed to parse GLB, error: %d" % err)
|
|
return null
|
|
|
|
print("GLBLoader: parsed successfully (%d animations, %d meshes)" % [
|
|
state.animations.size(), state.meshes.size()])
|
|
|
|
# Generate scene — remove_immutable_tracks=false preserves all bone
|
|
# animation tracks (default true strips tracks where rest==pose → T-pose).
|
|
var scene = gltf.generate_scene(state, 30, false, false)
|
|
if not scene:
|
|
print("GLBLoader: failed to generate scene")
|
|
return null
|
|
|
|
print("GLBLoader: generated scene: %s" % scene.name)
|
|
return scene
|