Files
Papay-Shooter/debug/visual_capture.gd
T
Nicholas ButzkeandClaude Fable 5 7920aecec5 feat: player animation quality pass — aim follow, reload/throw, recoil
Third-person player characters now animate every action with readable
intent (all networked, cel style preserved):

- Look-around: upper body follows the owner's camera pitch — distributed
  over spine/neck/head so aiming up/down reads on the whole silhouette
  (driven by the already-synced HeadPivot rotation on remotes)
- Reload: the baked PistolReload clip plays as a one-shot whenever the
  equipped weapon starts reloading (local edge-detect poll -> new synced
  action counter replays it on every peer)
- Grenade throw: new Throw clip baked from the library's overhead swing
  (Sword_Attack retarget); triggers on throw, synced the same way
- Shot recoil: visible kick on the shooter's model (shoulders snap back,
  forearms rise, fast decay) driven by the existing fire-effects RPC —
  remote players' shots now look like shots
- Slide: trailing arm braces against the ground for balance on top of the
  feet-first pose
- Armed idle confirmed in capture: weapon held at ready, not arms-down

Plumbing: synced_action/synced_action_seq added to all three player
spawners' replication configs; ACTIONS table on the model maps names to
clips + lock times.

Verified: 18 clips resolve (incl Throw), smoke 0 failures, movement
11/11, acoustics PASSED; third-person run + armed idle captures clean.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 12:03:30 -04:00

73 lines
2.2 KiB
GDScript

extends SceneTree
## Dev tool: launch with rendering, screenshot the main menu and a level,
## then quit. Run:
## godot --path . -s res://debug/visual_capture.gd -- <output_dir> [scene_path]
## Screenshots land in <output_dir>/shot_menu.png, shot_level.png, shot_tp_run.png
## and shot_aerial.png. scene_path defaults to the test level.
var _frames := 0
var _out_dir := "."
var _scene := "res://scenes/maps/test_level/test_level.tscn"
func _initialize() -> void:
var args := OS.get_cmdline_user_args()
if args.size() > 0:
_out_dir = args[0]
if args.size() > 1:
_scene = args[1]
change_scene_to_file("res://ui/main_menu/main_menu.tscn")
func _process(_delta: float) -> bool:
_frames += 1
if _frames == 60:
_shot("menu")
var nm = root.get_node_or_null("NetworkManager")
if nm and nm.has_method("start_singleplayer_match"):
nm.start_singleplayer_match("Deathmatch")
change_scene_to_file(_scene)
elif _frames == 240:
_shot("level")
# Third person + run forward, to eyeball model grounding and animation
for p in root.find_children("*", "CharacterBody3D", true, false):
if p.has_method("set_third_person") and p.is_multiplayer_authority():
p.set_third_person(true)
Input.action_press("move_forward")
elif _frames == 300:
_shot("tp_run")
Input.action_release("move_forward")
elif _frames == 338:
_shot("tp_idle")
# Aerial overview of the whole map
var cam := root.get_camera_3d()
if cam:
var aerial := Camera3D.new()
current_scene.add_child(aerial)
if _scene.contains("neon_alley"):
aerial.far = 2000.0
aerial.global_position = Vector3(0, 400, 300)
else:
aerial.global_position = Vector3(0, 55, 42)
aerial.look_at(Vector3(0, 0, -5), Vector3.UP)
aerial.current = true
elif _frames == 340:
_shot("aerial")
# Fixed street-level view for deterministic style comparisons
var cam := root.get_camera_3d()
if cam:
cam.global_position = Vector3(3, 2.2, 20)
cam.look_at(Vector3(-8, 4, -18), Vector3.UP)
elif _frames == 360:
_shot("street")
return true
return false
func _shot(tag: String) -> void:
var img := root.get_viewport().get_texture().get_image()
var path := _out_dir + "/shot_" + tag + ".png"
img.save_png(path)
print("visual_capture: saved ", path)