feat: third-person camera toggle (V) + fix first-person showing inside the model
The first-person head-shrink (SkeletonModifier3D) left the camera looking at the inside of the neck/torso/hair. Replaced it: the owner's model now renders shadows-only in first person (clean FPS view, still fully animated and visible to others and in shadows). Added a third-person toggle (V / toggle_camera_view) so you can actually see your own animated model — the easiest way to verify a new skin's animations: - over-the-shoulder SpringArm3D camera (wall-collision aware) on the local player; press V to swap, press again to return - firing still uses the first-person camera, so aim is unchanged - reveals the owner's model via set_owner_visible() on both SkinnedPlayerModel and HumanoidModel; hides the first-person weapon viewmodel while in TP - death forces first person so the toggle can't fight the death cam Smoke test extended to cover the toggle (camera boom created, becomes current, round-trips) — 28/28 checks pass for both color and GLB skins. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
64bbbf93c6
commit
e2fbc424a7
@@ -11,6 +11,12 @@ var camera: Camera3D = null
|
||||
var flashlight: SpotLight3D = null
|
||||
var _damage_layer: CanvasLayer = null
|
||||
|
||||
# Third-person view toggle (local player only). The first-person camera keeps
|
||||
# handling aim/firing; the toggle only changes which camera renders and whether
|
||||
# the owner sees their own model.
|
||||
var third_person: bool = false
|
||||
var _tp_camera: Camera3D = null
|
||||
|
||||
# Health and Shield
|
||||
var max_health: float = 100.0
|
||||
var health: float = 100.0
|
||||
@@ -132,10 +138,56 @@ func _ready() -> void:
|
||||
if skin_mgr:
|
||||
synced_skin_id = skin_mgr.active_skin_id
|
||||
synced_position = position
|
||||
_setup_third_person_camera()
|
||||
# Build the visual model for whatever skin is selected (remote peers get
|
||||
# the id via the synchronizer and rebuild in _process when it arrives).
|
||||
_apply_skin_model(synced_skin_id)
|
||||
|
||||
|
||||
## Over-the-shoulder camera for the local player, on a spring arm so it doesn't
|
||||
## clip through walls. The first-person camera still exists and handles firing;
|
||||
## this one only renders when third_person is on.
|
||||
func _setup_third_person_camera() -> void:
|
||||
if not head_pivot or not is_instance_valid(camera):
|
||||
return
|
||||
var boom := SpringArm3D.new()
|
||||
boom.name = "ThirdPersonBoom"
|
||||
boom.spring_length = 3.2
|
||||
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)
|
||||
|
||||
_tp_camera = Camera3D.new()
|
||||
_tp_camera.name = "ThirdPersonCamera"
|
||||
_tp_camera.fov = camera.fov
|
||||
_tp_camera.current = false
|
||||
boom.add_child(_tp_camera)
|
||||
|
||||
|
||||
## Toggle between first- and third-person. Firing keeps using the first-person
|
||||
## camera, so aim is unchanged; only rendering and self-visibility change.
|
||||
func set_third_person(on: bool) -> void:
|
||||
if not is_multiplayer_authority():
|
||||
return
|
||||
third_person = on
|
||||
if is_instance_valid(_tp_camera):
|
||||
_tp_camera.current = on
|
||||
if is_instance_valid(camera):
|
||||
camera.current = not on
|
||||
# Reveal / hide the owner's own model.
|
||||
var visual = get_visual_model()
|
||||
if visual and visual.has_method("set_owner_visible"):
|
||||
visual.set_owner_visible(on)
|
||||
# Hide the first-person weapon viewmodel in third person (it would float in
|
||||
# the middle of the screen); the third-person weapon on the model shows.
|
||||
if is_instance_valid(camera):
|
||||
var wman = camera.get_node_or_null("WeaponManager")
|
||||
if wman and "canvas_layer" in wman and is_instance_valid(wman.canvas_layer):
|
||||
wman.canvas_layer.visible = not on
|
||||
|
||||
## Returns the node that visually represents this player (skinned GLB model
|
||||
## if the active skin has one, otherwise the procedural HumanoidModel).
|
||||
func get_visual_model() -> Node3D:
|
||||
@@ -760,6 +812,9 @@ func _physics_process(_delta: float) -> void:
|
||||
if Input.is_action_just_pressed("toggle_flashlight") and is_instance_valid(flashlight):
|
||||
flashlight.visible = !flashlight.visible
|
||||
|
||||
if Input.is_action_just_pressed("toggle_camera_view"):
|
||||
set_third_person(not third_person)
|
||||
|
||||
var machine := _ensure_machine()
|
||||
if machine:
|
||||
machine.input_dir = raw_input
|
||||
@@ -1026,7 +1081,11 @@ func _setup_hud() -> void:
|
||||
func die(impulse: Vector3 = Vector3.ZERO) -> void:
|
||||
if is_dead: return
|
||||
is_dead = true
|
||||
|
||||
|
||||
# Return to first person so the toggle state doesn't fight the death cam.
|
||||
if is_multiplayer_authority() and third_person:
|
||||
set_third_person(false)
|
||||
|
||||
# Disable movement state machine inputs
|
||||
if _machine:
|
||||
_machine.process_mode = Node.PROCESS_MODE_DISABLED
|
||||
|
||||
Reference in New Issue
Block a user