- Third-person camera chain is now HeadPivot > OrbitPivot > ShoulderOffset (0.55m right, 0.12m up) > SpringArm(2.6m, 14 deg) — the character sits slightly left of screen centre over-the-shoulder instead of dead centre behind. Aim is unchanged (still the first-person camera). - Hold Alt in third person to orbit the camera freely around the character with the mouse (yaw wraps, pitch clamped -69..+29 deg); the character keeps facing and aiming where it was. Release Alt and the camera springs back behind the shoulder. - debug/orbit_capture.gd: screenshots the OTS framing, a driven orbit, and the spring-back for visual regression. Co-Authored-By: Claude Fable 5 <[email protected]>
60 lines
1.9 KiB
GDScript
60 lines
1.9 KiB
GDScript
extends SceneTree
|
|
|
|
## Dev tool: screenshot the third-person over-the-shoulder framing, the Alt
|
|
## free-look orbit, and the spring-back. Run:
|
|
## godot --path . --windowed --resolution 1280x720 -s res://debug/orbit_capture.gd -- <out_dir>
|
|
|
|
var _frames := 0
|
|
var _out_dir := "."
|
|
var _player: Node = null
|
|
var _rig: Node = null
|
|
|
|
|
|
func _initialize() -> void:
|
|
var args := OS.get_cmdline_user_args()
|
|
if args.size() > 0:
|
|
_out_dir = args[0]
|
|
change_scene_to_file("res://ui/main_menu/main_menu.tscn")
|
|
|
|
|
|
func _process(_delta: float) -> bool:
|
|
_frames += 1
|
|
if _frames == 40:
|
|
var sm = root.get_node_or_null("SkinManager")
|
|
if sm:
|
|
sm.set_active_skin("taila")
|
|
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("res://scenes/maps/test_level/test_level.tscn")
|
|
elif _frames == 160:
|
|
for p in root.find_children("*", "CharacterBody3D", true, false):
|
|
if p.has_method("set_third_person") and p.is_multiplayer_authority():
|
|
_player = p
|
|
break
|
|
if not _player:
|
|
printerr("orbit_capture: no player")
|
|
return true
|
|
_rig = _player.get_node_or_null("HeadPivot")
|
|
_player.set_third_person(true)
|
|
elif _frames == 220:
|
|
_shot("ots_default") # over-the-shoulder framing, no orbit
|
|
elif _frames > 220 and _frames <= 280:
|
|
# Simulate holding Alt: force the orbit angles every frame (the
|
|
# spring-back fights us since Alt isn't really down, so keep writing).
|
|
_rig._orbit_yaw = 1.9
|
|
_rig._orbit_pitch = -0.25
|
|
elif _frames == 281:
|
|
_shot("orbit_side") # camera swung around toward the front
|
|
elif _frames == 320:
|
|
_shot("sprung_back") # ~0.65s after release: should be behind again
|
|
return true
|
|
return false
|
|
|
|
|
|
func _shot(tag: String) -> void:
|
|
var img := root.get_viewport().get_texture().get_image()
|
|
var path := _out_dir + "/" + tag + ".png"
|
|
img.save_png(path)
|
|
print("orbit_capture: saved ", path)
|