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]>
76 lines
2.1 KiB
GDScript
76 lines
2.1 KiB
GDScript
extends StaticBody3D
|
|
class_name TargetDummy
|
|
|
|
var visual_node: Node3D
|
|
var dps_label: Label3D
|
|
|
|
# Array of dictionaries: { "time": float, "amount": float }
|
|
var damage_history: Array = []
|
|
|
|
func _ready() -> void:
|
|
add_to_group("enemies") # Just in case we need to filter
|
|
|
|
# Collision Shape
|
|
var shape = CollisionShape3D.new()
|
|
shape.shape = CapsuleShape3D.new()
|
|
shape.shape.radius = 0.5
|
|
shape.shape.height = 2.0
|
|
shape.position = Vector3(0, 1.0, 0)
|
|
add_child(shape)
|
|
|
|
# Humanoid Model
|
|
visual_node = load("res://characters/humanoid_model.gd").new()
|
|
visual_node.name = "HumanoidModel"
|
|
visual_node.color = Color(0.8, 0.2, 0.2)
|
|
add_child(visual_node)
|
|
|
|
# DPS Label
|
|
dps_label = Label3D.new()
|
|
dps_label.text = "DPS: 0"
|
|
dps_label.pixel_size = 0.01
|
|
dps_label.billboard = BaseMaterial3D.BILLBOARD_ENABLED
|
|
dps_label.position = Vector3(0, 2.3, 0)
|
|
dps_label.modulate = Color(1, 0.8, 0)
|
|
add_child(dps_label)
|
|
|
|
func take_damage(amount: float, hit_position: Vector3, source: Node = null, _impulse: Vector3 = Vector3.ZERO) -> void:
|
|
var now = Time.get_ticks_msec() / 1000.0
|
|
damage_history.append({"time": now, "amount": amount})
|
|
|
|
if source and source.has_method("spawn_damage_number"):
|
|
source.spawn_damage_number(amount, hit_position)
|
|
|
|
_wiggle()
|
|
|
|
func _process(_delta: float) -> void:
|
|
_update_dps()
|
|
|
|
func _update_dps() -> void:
|
|
var now = Time.get_ticks_msec() / 1000.0
|
|
var total_damage: float = 0.0
|
|
|
|
# Keep only last 5 seconds
|
|
var valid_history = []
|
|
for dmg in damage_history:
|
|
if now - dmg["time"] <= 5.0:
|
|
valid_history.append(dmg)
|
|
total_damage += dmg["amount"]
|
|
|
|
damage_history = valid_history
|
|
|
|
if damage_history.size() > 0:
|
|
var dps = total_damage / 5.0
|
|
dps_label.text = "DPS: %.1f" % dps
|
|
else:
|
|
dps_label.text = "DPS: 0"
|
|
|
|
func _wiggle() -> void:
|
|
# Small tween to shake the dummy
|
|
var tween = create_tween()
|
|
var orig_pos = Vector3.ZERO
|
|
var wiggle_dir = Vector3(randf_range(-0.1, 0.1), 0, randf_range(-0.1, 0.1))
|
|
|
|
tween.tween_property(visual_node, "position", orig_pos + wiggle_dir, 0.05)
|
|
tween.tween_property(visual_node, "position", orig_pos - wiggle_dir, 0.05)
|
|
tween.tween_property(visual_node, "position", orig_pos, 0.05)
|