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 _dash_fov_kick: float = 0.0 # extra FOV from dashing, decays
|
||||||
var _machine: MovementStateMachine = null
|
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:
|
func _ready() -> void:
|
||||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
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:
|
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 is_ads = camera and camera.fov < SettingsManager.world_fov - 5.0
|
||||||
var sens = SettingsManager.ads_sensitivity if is_ads else SettingsManager.mouse_sensitivity
|
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)
|
# Yaw: rotate the player (parent)
|
||||||
get_parent().rotate_y(-event.relative.x * sens)
|
get_parent().rotate_y(-event.relative.x * sens)
|
||||||
# Pitch: rotate this pivot
|
# Pitch: rotate this pivot
|
||||||
@@ -84,6 +99,14 @@ func _input(event: InputEvent) -> void:
|
|||||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
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:
|
func _process(delta: float) -> void:
|
||||||
if not camera or not params:
|
if not camera or not params:
|
||||||
return
|
return
|
||||||
@@ -92,6 +115,16 @@ func _process(delta: float) -> void:
|
|||||||
if not player:
|
if not player:
|
||||||
return
|
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:
|
if not _machine:
|
||||||
_connect_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.
|
# Third-person toggle: camera swap + owner model visibility.
|
||||||
if player.has_method("set_third_person"):
|
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")
|
_check(tp_cam != null, "third-person camera boom created")
|
||||||
player.set_third_person(true)
|
player.set_third_person(true)
|
||||||
await process_frame
|
await process_frame
|
||||||
|
|||||||
@@ -168,15 +168,27 @@ func _ready() -> void:
|
|||||||
func _setup_third_person_camera() -> void:
|
func _setup_third_person_camera() -> void:
|
||||||
if not head_pivot or not is_instance_valid(camera):
|
if not head_pivot or not is_instance_valid(camera):
|
||||||
return
|
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()
|
var boom := SpringArm3D.new()
|
||||||
boom.name = "ThirdPersonBoom"
|
boom.name = "ThirdPersonBoom"
|
||||||
boom.spring_length = 3.2
|
boom.spring_length = 2.6
|
||||||
boom.margin = 0.3
|
boom.margin = 0.3
|
||||||
boom.collision_mask = 1 # environment only
|
boom.collision_mask = 1 # environment only
|
||||||
boom.add_excluded_object(get_rid())
|
boom.add_excluded_object(get_rid())
|
||||||
# Aim the arm up-and-back from the head so the camera sits behind/above.
|
# Aim the arm up-and-back from the shoulder so the camera sits behind/above.
|
||||||
boom.rotation_degrees = Vector3(20, 0, 0)
|
boom.rotation_degrees = Vector3(14, 0, 0)
|
||||||
head_pivot.add_child(boom)
|
shoulder.add_child(boom)
|
||||||
|
|
||||||
_tp_camera = Camera3D.new()
|
_tp_camera = Camera3D.new()
|
||||||
_tp_camera.name = "ThirdPersonCamera"
|
_tp_camera.name = "ThirdPersonCamera"
|
||||||
|
|||||||
Reference in New Issue
Block a user