feat: Fortnite-style OTS camera + Alt free-look orbit in third person
- 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]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b0f26e6dde
commit
87b8ae70df
@@ -0,0 +1,59 @@
|
||||
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)
|
||||
@@ -98,7 +98,8 @@ func _test_spawn_with_skin(skin_id: String, expect_skinned: bool) -> void:
|
||||
|
||||
# Third-person toggle: camera swap + owner model visibility.
|
||||
if player.has_method("set_third_person"):
|
||||
var tp_cam = player.get_node_or_null("HeadPivot/ThirdPersonBoom/ThirdPersonCamera")
|
||||
var tp_cam = player.get_node_or_null(
|
||||
"HeadPivot/OrbitPivot/ShoulderOffset/ThirdPersonBoom/ThirdPersonCamera")
|
||||
_check(tp_cam != null, "third-person camera boom created")
|
||||
player.set_third_person(true)
|
||||
await process_frame
|
||||
|
||||
Reference in New Issue
Block a user