Feat/visual audio overhaul #21
@@ -28,6 +28,14 @@ var _pitch_impulse: float = 0.0 # extra camera pitch in radians, decays
|
||||
var _dash_fov_kick: float = 0.0 # extra FOV from dashing, decays
|
||||
var _machine: MovementStateMachine = null
|
||||
|
||||
# Third-person free-look: hold Alt to orbit the camera around the character
|
||||
# without turning the character or aim; release and it springs back behind.
|
||||
var _orbit_yaw: float = 0.0
|
||||
var _orbit_pitch: float = 0.0
|
||||
const ORBIT_PITCH_MIN := -1.2 # radians (looking down from above)
|
||||
const ORBIT_PITCH_MAX := 0.5 # radians (looking up from below)
|
||||
const ORBIT_RETURN_SPEED := 10.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||
@@ -71,6 +79,13 @@ func _input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
||||
var is_ads = camera and camera.fov < SettingsManager.world_fov - 5.0
|
||||
var sens = SettingsManager.ads_sensitivity if is_ads else SettingsManager.mouse_sensitivity
|
||||
if _free_looking():
|
||||
# Alt held in third person: orbit the camera around the character;
|
||||
# the character keeps facing (and aiming) where it was.
|
||||
_orbit_yaw = wrapf(_orbit_yaw - event.relative.x * sens, -PI, PI)
|
||||
_orbit_pitch = clampf(_orbit_pitch - event.relative.y * sens,
|
||||
ORBIT_PITCH_MIN, ORBIT_PITCH_MAX)
|
||||
else:
|
||||
# Yaw: rotate the player (parent)
|
||||
get_parent().rotate_y(-event.relative.x * sens)
|
||||
# Pitch: rotate this pivot
|
||||
@@ -84,6 +99,14 @@ func _input(event: InputEvent) -> void:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||
|
||||
|
||||
## True while the player is holding Alt to look around in third person.
|
||||
func _free_looking() -> bool:
|
||||
var p := get_parent()
|
||||
return p != null and "third_person" in p and p.third_person \
|
||||
and Input.is_key_pressed(KEY_ALT) \
|
||||
and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not camera or not params:
|
||||
return
|
||||
@@ -92,6 +115,16 @@ func _process(delta: float) -> void:
|
||||
if not player:
|
||||
return
|
||||
|
||||
# ── Third-person free-look orbit ──────────────────────────────────────
|
||||
var orbit := get_node_or_null("OrbitPivot")
|
||||
if orbit:
|
||||
if not _free_looking():
|
||||
# Spring back behind the character once Alt is released.
|
||||
var t := 1.0 - exp(-ORBIT_RETURN_SPEED * delta)
|
||||
_orbit_yaw = lerpf(_orbit_yaw, 0.0, t)
|
||||
_orbit_pitch = lerpf(_orbit_pitch, 0.0, t)
|
||||
orbit.rotation = Vector3(_orbit_pitch, _orbit_yaw, 0.0)
|
||||
|
||||
if not _machine:
|
||||
_connect_machine()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -168,15 +168,27 @@ func _ready() -> void:
|
||||
func _setup_third_person_camera() -> void:
|
||||
if not head_pivot or not is_instance_valid(camera):
|
||||
return
|
||||
# Chain: head_pivot > OrbitPivot (Alt free-look, springs back to zero)
|
||||
# > ShoulderOffset (camera right of the spine — the character sits
|
||||
# slightly LEFT of screen centre, Fortnite-style) > SpringArm.
|
||||
var orbit := Node3D.new()
|
||||
orbit.name = "OrbitPivot"
|
||||
head_pivot.add_child(orbit)
|
||||
|
||||
var shoulder := Node3D.new()
|
||||
shoulder.name = "ShoulderOffset"
|
||||
shoulder.position = Vector3(0.55, 0.12, 0.0)
|
||||
orbit.add_child(shoulder)
|
||||
|
||||
var boom := SpringArm3D.new()
|
||||
boom.name = "ThirdPersonBoom"
|
||||
boom.spring_length = 3.2
|
||||
boom.spring_length = 2.6
|
||||
boom.margin = 0.3
|
||||
boom.collision_mask = 1 # environment only
|
||||
boom.add_excluded_object(get_rid())
|
||||
# Aim the arm up-and-back from the head so the camera sits behind/above.
|
||||
boom.rotation_degrees = Vector3(20, 0, 0)
|
||||
head_pivot.add_child(boom)
|
||||
# Aim the arm up-and-back from the shoulder so the camera sits behind/above.
|
||||
boom.rotation_degrees = Vector3(14, 0, 0)
|
||||
shoulder.add_child(boom)
|
||||
|
||||
_tp_camera = Camera3D.new()
|
||||
_tp_camera.name = "ThirdPersonCamera"
|
||||
|
||||
Reference in New Issue
Block a user