extends SceneTree ## Deterministic third-person run-to-stop capture. ## ## Run: ## godot --path . --resolution 1280x720 \ ## --script res://debug/locomotion_stop_capture.gd -- [skin_id] const DT := 1.0 / 60.0 const RUN_FRAMES := 90 var _out_dir := "." var _skin_id := "miku" var _frames := 0 var _player: CharacterBody3D = null var _model: SkinnedPlayerModel = null var _camera: Camera3D = null var _motion_frame := 0 var _stop_frame := -1 var _worst_run_stride_error := 0.0 var _worst_stop_stride := 0.0 var _failures: Array[String] = [] func _initialize() -> void: Engine.max_fps = 60 var args := OS.get_cmdline_user_args() if args.size() > 0: _out_dir = args[0] if args.size() > 1: _skin_id = args[1] if not _out_dir.is_absolute_path(): _out_dir = ProjectSettings.globalize_path(_out_dir) var mkdir_error := DirAccess.make_dir_recursive_absolute(_out_dir) if mkdir_error != OK: printerr("LOCO_STOP could not create '%s' (error %d)" % [ _out_dir, mkdir_error]) quit(mkdir_error) return change_scene_to_file("res://ui/main_menu/main_menu.tscn") func _process(_delta: float) -> bool: _frames += 1 if _frames == 40: var skin_manager = root.get_node_or_null("SkinManager") if skin_manager: skin_manager.set_active_skin(_skin_id) var network = root.get_node_or_null("NetworkManager") if network and network.has_method("start_singleplayer_match"): network.start_singleplayer_match(GameMode.DEATHMATCH) change_scene_to_file("res://scenes/maps/test_level/test_level.tscn") return false if _frames < 160: return false if _player == null: if not _setup(): _finish() return true _motion_frame += 1 if _motion_frame <= RUN_FRAMES: _model.set_locomotion(0.0, 1.0, 0.0) _model.update_state("ground", 9.0, false) _worst_run_stride_error = maxf(_worst_run_stride_error, absf(_model.locomotion_effective_speed_debug() - 9.0)) if _motion_frame == RUN_FRAMES - 1: _snap("loco_stop_00_run") else: if _stop_frame < 0: _stop_frame = 0 else: _stop_frame += 1 _model.set_locomotion(0.0, 0.0, 0.0) _model.update_state("ground", 0.0, false) _worst_stop_stride = maxf(_worst_stop_stride, _model.locomotion_effective_speed_debug()) _model._process(DT) if _stop_frame >= 0: match _stop_frame: 3: _snap("loco_stop_01_idle_blend") 9: _snap("loco_stop_02_idle_arrival") 20: _snap("loco_stop_03_idle") 50: _snap("loco_stop_04_settled") if _worst_run_stride_error > 0.02: _failures.append("run stride diverged from physical speed") if _worst_stop_stride > 0.02: _failures.append("authored stride did not stop immediately") if absf(_model.get_brake_debug()) > 0.001: _failures.append("procedural brake pose is still active") _finish() return true return false func _setup() -> bool: for candidate in root.find_children("*", "CharacterBody3D", true, false): if candidate.has_method("get_visual_model") \ and candidate.is_multiplayer_authority(): _player = candidate break if _player == null: _failures.append("local player was not created") return false _model = _player.get_node_or_null("SkinnedModel") if _model == null or not _model.loaded: _failures.append("skinned model '%s' was not ready" % _skin_id) return false _player.set_physics_process(false) _player.set_process(false) _model.set_process(false) _player.global_position = Vector3(0.0, 1.2, 14.0) _player.rotation = Vector3.ZERO _model.set_owner_visible(true) if _model.has_method("set_weapon"): _model.set_weapon("res://weapons/ak47.gd") # The match HUD and the isolated first-person SubViewport are separate # CanvasLayers, so a cull mask alone cannot keep them out of this shot. for layer in root.find_children("*", "CanvasLayer", true, false): layer.visible = false _camera = Camera3D.new() _camera.cull_mask &= ~(1 << 19) current_scene.add_child(_camera) _camera.global_position = _player.global_position + Vector3(1.8, 0.85, -1.9) _camera.look_at(_player.global_position + Vector3(0.0, 0.45, 0.0), Vector3.UP) _camera.current = true return true func _snap(tag: String) -> void: var image := root.get_viewport().get_texture().get_image() var path := _out_dir.path_join(tag + ".png") var error := image.save_png(path) if error == OK: print("LOCO_STOP saved ", path) else: _failures.append("could not save %s (error %d)" % [path, error]) func _finish() -> void: if _failures.is_empty() and _stop_frame >= 50: print("LOCO_STOP PASS: run error %.5f, stop stride %.5f, procedural brake %.5f" % [ _worst_run_stride_error, _worst_stop_stride, _model.get_brake_debug()]) quit(0) else: for failure in _failures: push_error("LOCO_STOP FAIL: " + failure) quit(1)