feat(miku): proper rigged model from Sketchfab + Blender
Pipeline: 1. Downloaded TDA-style Miku model from Sketchfab (19MB FBX, 38K verts) 2. Imported to Blender, scaled to Godot humanoid proportions (1.8u tall) 3. Created clean 17-bone humanoid armature (Hips/Spine/Chest/Head/Arms/Legs) 4. Auto-weight-painted mesh to armature 5. Added Idle (breath bounce) and Run (cycle) animations 6. Exported as GLB with embedded animations (3MB) New: - SkinnedPlayerModel class: loads GLB with own armature+animations - Player 1 spawns with Miku model, others get procedural humanoid - Completely separate from procedural model system Files: - assets/characters/skins/miku_rigged_final.glb (3MB, 48K verts, 17 bones, 2 anims) - characters/skinned_player_model.gd - Removed old procedural GLB attempts
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,88 @@
|
|||||||
|
extends Node3D
|
||||||
|
class_name SkinnedPlayerModel
|
||||||
|
|
||||||
|
## A player model loaded from a GLB file with its own armature and animations.
|
||||||
|
## Replaces the procedural HumanoidModel for characters with custom skins.
|
||||||
|
|
||||||
|
@export var model_path: String = ""
|
||||||
|
|
||||||
|
var skeleton: Skeleton3D
|
||||||
|
var animation_player: AnimationPlayer
|
||||||
|
var animation_tree: AnimationTree
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
if model_path != "":
|
||||||
|
load_model(model_path)
|
||||||
|
|
||||||
|
func load_model(path: String) -> void:
|
||||||
|
# Clear existing children
|
||||||
|
for child in get_children():
|
||||||
|
child.queue_free()
|
||||||
|
|
||||||
|
var loaded = load(path)
|
||||||
|
if not loaded:
|
||||||
|
print("SkinnedPlayerModel: failed to load %s" % path)
|
||||||
|
return
|
||||||
|
|
||||||
|
var scene = loaded.instantiate()
|
||||||
|
if not scene:
|
||||||
|
print("SkinnedPlayerModel: failed to instantiate %s" % path)
|
||||||
|
return
|
||||||
|
|
||||||
|
add_child(scene)
|
||||||
|
|
||||||
|
# Find skeleton and animation player
|
||||||
|
skeleton = find_skeleton(scene)
|
||||||
|
animation_player = find_animation_player(scene)
|
||||||
|
|
||||||
|
if animation_player:
|
||||||
|
print("SkinnedPlayerModel: loaded '%s' with %d animations" % [
|
||||||
|
path, animation_player.get_animation_list().size()
|
||||||
|
])
|
||||||
|
for anim in animation_player.get_animation_list():
|
||||||
|
print(" - %s" % anim)
|
||||||
|
else:
|
||||||
|
print("SkinnedPlayerModel: no animation player found")
|
||||||
|
|
||||||
|
func find_skeleton(node: Node) -> Skeleton3D:
|
||||||
|
if node is Skeleton3D:
|
||||||
|
return node
|
||||||
|
for child in node.get_children():
|
||||||
|
var result = find_skeleton(child)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
return null
|
||||||
|
|
||||||
|
func find_animation_player(node: Node) -> AnimationPlayer:
|
||||||
|
if node is AnimationPlayer:
|
||||||
|
return node
|
||||||
|
for child in node.get_children():
|
||||||
|
var result = find_animation_player(child)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
return null
|
||||||
|
|
||||||
|
func play_animation(name: String) -> void:
|
||||||
|
if animation_player and animation_player.has_animation(name):
|
||||||
|
animation_player.play(name)
|
||||||
|
else:
|
||||||
|
print("SkinnedPlayerModel: animation '%s' not found" % name)
|
||||||
|
|
||||||
|
func update_state(state: String, speed: float, is_crouching: bool = false) -> void:
|
||||||
|
if not animation_player:
|
||||||
|
return
|
||||||
|
|
||||||
|
match state:
|
||||||
|
"ground", "idle":
|
||||||
|
if speed > 1.0:
|
||||||
|
play_animation("Run")
|
||||||
|
else:
|
||||||
|
play_animation("Idle")
|
||||||
|
"air":
|
||||||
|
play_animation("Idle") # TODO: add jump/fall anim
|
||||||
|
"crouch":
|
||||||
|
play_animation("Idle") # TODO: add crouch anim
|
||||||
|
"slide":
|
||||||
|
play_animation("Run") # TODO: add slide anim
|
||||||
|
_:
|
||||||
|
play_animation("Idle")
|
||||||
@@ -369,7 +369,15 @@ func _spawn_player(pid: int) -> CharacterBody3D:
|
|||||||
if mover_script:
|
if mover_script:
|
||||||
player.set_script(mover_script)
|
player.set_script(mover_script)
|
||||||
|
|
||||||
# Humanoid Model for Player
|
# Visual Model for Player
|
||||||
|
# Player 1 gets the Miku skinned model, others get procedural humanoid
|
||||||
|
if pid == 1:
|
||||||
|
var skinned = load("res://characters/skinned_player_model.gd").new()
|
||||||
|
skinned.name = "SkinnedModel"
|
||||||
|
skinned.model_path = "res://assets/characters/skins/miku_rigged_final.glb"
|
||||||
|
skinned.position = Vector3(0, -0.9, 0)
|
||||||
|
player.add_child(skinned)
|
||||||
|
else:
|
||||||
var humanoid = load("res://characters/humanoid_model.gd").new()
|
var humanoid = load("res://characters/humanoid_model.gd").new()
|
||||||
humanoid.name = "HumanoidModel"
|
humanoid.name = "HumanoidModel"
|
||||||
humanoid.color = Color(0.2, 0.4, 0.8)
|
humanoid.color = Color(0.2, 0.4, 0.8)
|
||||||
@@ -377,20 +385,6 @@ func _spawn_player(pid: int) -> CharacterBody3D:
|
|||||||
humanoid.position = Vector3(0, -0.9, 0)
|
humanoid.position = Vector3(0, -0.9, 0)
|
||||||
player.add_child(humanoid)
|
player.add_child(humanoid)
|
||||||
|
|
||||||
# Apply skin (Miku for player 1, default for others)
|
|
||||||
if pid == 1:
|
|
||||||
var miku_skin = PlayerSkin.new()
|
|
||||||
miku_skin.skin_name = "Miku"
|
|
||||||
miku_skin.model_path = "res://assets/characters/skins/miku_rigged.glb"
|
|
||||||
miku_skin.color_tint = Color(0.0, 0.75, 0.75)
|
|
||||||
humanoid.apply_skin(miku_skin)
|
|
||||||
else:
|
|
||||||
# Default blue skin for other players
|
|
||||||
var default_skin = PlayerSkin.new()
|
|
||||||
default_skin.skin_name = "Default"
|
|
||||||
default_skin.color_tint = Color(0.2, 0.4, 0.8)
|
|
||||||
humanoid.apply_skin(default_skin)
|
|
||||||
|
|
||||||
# Movement State Machine
|
# Movement State Machine
|
||||||
var sm := Node.new()
|
var sm := Node.new()
|
||||||
sm.name = "MovementStateMachine"
|
sm.name = "MovementStateMachine"
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 146 KiB |
Reference in New Issue
Block a user