161 lines
4.9 KiB
GDScript
161 lines
4.9 KiB
GDScript
extends SceneTree
|
|
|
|
## Deterministic third-person run-to-stop capture.
|
|
##
|
|
## Run:
|
|
## godot --path . --resolution 1280x720 \
|
|
## --script res://debug/locomotion_stop_capture.gd -- <out_dir> [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 _first_stop_stride := 0.0
|
|
var _settled_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)
|
|
if _motion_frame > 30:
|
|
_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)
|
|
if _stop_frame == 0:
|
|
_first_stop_stride = _model.locomotion_effective_speed_debug()
|
|
_settled_stop_stride = _model.locomotion_effective_speed_debug()
|
|
_model._process(DT)
|
|
|
|
if _stop_frame >= 0:
|
|
match _stop_frame:
|
|
3:
|
|
_snap("loco_stop_01_idle_blend")
|
|
12:
|
|
_snap("loco_stop_02_idle_arrival")
|
|
30:
|
|
_snap("loco_stop_03_idle")
|
|
50:
|
|
_snap("loco_stop_04_settled")
|
|
if _worst_run_stride_error > 0.20:
|
|
_failures.append("run stride diverged from physical speed")
|
|
if _first_stop_stride < 4.0:
|
|
_failures.append("run pose snapped to idle on the stop frame")
|
|
if _settled_stop_stride > 0.5:
|
|
_failures.append("authored stride did not ease into idle")
|
|
if absf(_model.get_brake_debug()) > 0.001:
|
|
_failures.append("procedural brake pose is still active")
|
|
_finish()
|
|
return false
|
|
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 ease %.5f -> %.5f, procedural brake %.5f" % [
|
|
_worst_run_stride_error, _first_stop_stride, _settled_stop_stride,
|
|
_model.get_brake_debug()])
|
|
quit(0)
|
|
else:
|
|
for failure in _failures:
|
|
push_error("LOCO_STOP FAIL: " + failure)
|
|
quit(1)
|