Stabilize camera input across fishing and radial menus
This commit is contained in:
parent
6fd1aa0388
commit
8f8398cbcd
4 changed files with 147 additions and 43 deletions
|
|
@ -375,7 +375,6 @@ class ShowcaseCameraSnapshot:
|
|||
var pitch_rotation: Vector3
|
||||
var spring_length: float
|
||||
var target_zoom: float
|
||||
var camera_input_enabled: bool
|
||||
var camera_dragging: bool
|
||||
|
||||
|
||||
|
|
@ -477,6 +476,7 @@ var _free_camera_yaw: float = 0.0
|
|||
var _free_camera_pitch: float = 0.0
|
||||
var _movement_enabled: bool = true
|
||||
var _local_input_suppressors: Dictionary[StringName, bool] = {}
|
||||
var _camera_input_suppressors: Dictionary[StringName, bool] = {}
|
||||
var _water_recovery_active: bool = false
|
||||
var _remote_recovery_presentation_active: bool = false
|
||||
var _remote_recovery_visual_origin: Vector3
|
||||
|
|
@ -1789,7 +1789,10 @@ func _update_free_camera_physics() -> void:
|
|||
if _free_camera == null or _free_camera_body == null:
|
||||
_set_free_camera_active(false)
|
||||
return
|
||||
if not _is_movement_input_enabled():
|
||||
# Freecam translation is camera input, not avatar movement. Fishing locks
|
||||
# the avatar after a cast, but the detached camera must remain movable.
|
||||
# Camera/menu input ownership still stops freecam while an overlay is active.
|
||||
if not _is_camera_input_enabled():
|
||||
_free_camera_body.velocity = Vector3.ZERO
|
||||
_free_camera_body.move_and_slide()
|
||||
return
|
||||
|
|
@ -2483,7 +2486,11 @@ func _is_movement_input_enabled() -> bool:
|
|||
|
||||
|
||||
func _is_camera_input_enabled() -> bool:
|
||||
return _camera_input_enabled and _local_input_suppressors.is_empty()
|
||||
return (
|
||||
_camera_input_enabled
|
||||
and _local_input_suppressors.is_empty()
|
||||
and _camera_input_suppressors.is_empty()
|
||||
)
|
||||
|
||||
|
||||
func set_camera_input_enabled(enabled: bool) -> void:
|
||||
|
|
@ -2492,6 +2499,19 @@ func set_camera_input_enabled(enabled: bool) -> void:
|
|||
_set_camera_dragging(false)
|
||||
|
||||
|
||||
func set_camera_input_suppressed(
|
||||
owner: StringName,
|
||||
suppressed: bool,
|
||||
) -> void:
|
||||
if owner.is_empty():
|
||||
return
|
||||
if suppressed:
|
||||
_camera_input_suppressors[owner] = true
|
||||
_set_camera_dragging(false)
|
||||
else:
|
||||
_camera_input_suppressors.erase(owner)
|
||||
|
||||
|
||||
func set_camera_active(active: bool) -> void:
|
||||
if _free_camera_active and _free_camera != null:
|
||||
_free_camera.current = active
|
||||
|
|
@ -3344,9 +3364,6 @@ func _complete_showcase_restore(
|
|||
_camera_pitch.rotation = _showcase_camera_snapshot.pitch_rotation
|
||||
_spring_arm.spring_length = _showcase_camera_snapshot.spring_length
|
||||
_target_zoom = _showcase_camera_snapshot.target_zoom
|
||||
_camera_input_enabled = (
|
||||
_showcase_camera_snapshot.camera_input_enabled
|
||||
)
|
||||
_set_camera_dragging(
|
||||
_showcase_camera_snapshot.camera_dragging
|
||||
and Input.is_action_pressed("camera_drag")
|
||||
|
|
@ -3367,9 +3384,7 @@ func _capture_showcase_camera_snapshot() -> void:
|
|||
_showcase_camera_snapshot.pitch_rotation = _camera_pitch.rotation
|
||||
_showcase_camera_snapshot.spring_length = _spring_arm.spring_length
|
||||
_showcase_camera_snapshot.target_zoom = _target_zoom
|
||||
_showcase_camera_snapshot.camera_input_enabled = _camera_input_enabled
|
||||
_showcase_camera_snapshot.camera_dragging = _camera_dragging
|
||||
_camera_input_enabled = false
|
||||
_set_camera_dragging(false)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -312,10 +312,23 @@ func _run() -> void:
|
|||
Input.parse_input_event(right_stick_motion)
|
||||
await process_frame
|
||||
|
||||
# Chat owns all movement while text entry is active, including developer
|
||||
# freecam movement which reads the same WASD actions as the player.
|
||||
var free_camera_body := player.get("_free_camera_body") as CharacterBody3D
|
||||
assert(free_camera_body != null)
|
||||
# A released fishing cast locks only the avatar. The detached camera must
|
||||
# continue to accept translation while that movement lock is active.
|
||||
player.set_movement_enabled(false)
|
||||
var cast_locked_camera_position: Vector3 = free_camera_body.global_position
|
||||
Input.action_press("move_forward")
|
||||
for _frame: int in 3:
|
||||
await physics_frame
|
||||
Input.action_release("move_forward")
|
||||
assert(not free_camera_body.global_position.is_equal_approx(
|
||||
cast_locked_camera_position
|
||||
))
|
||||
player.set_movement_enabled(true)
|
||||
|
||||
# Chat owns all movement while text entry is active, including developer
|
||||
# freecam movement which reads the same WASD actions as the player.
|
||||
chat_ui.open_chat()
|
||||
assert(chat_ui.is_open())
|
||||
var free_camera_position_before: Vector3 = free_camera_body.global_position
|
||||
|
|
|
|||
|
|
@ -30,8 +30,14 @@ func _run() -> void:
|
|||
|
||||
var player := main.get("_player") as Player
|
||||
var fishing_spot := main.get_node("%FishingSpot") as FishingSpot
|
||||
var game_ui := main.get_node("%GameUI") as GameUI
|
||||
var catalog := main.get("fish_catalog") as FishPool
|
||||
assert(player != null and fishing_spot != null and catalog != null)
|
||||
assert(
|
||||
player != null
|
||||
and fishing_spot != null
|
||||
and game_ui != null
|
||||
and catalog != null
|
||||
)
|
||||
fishing_spot.minimum_showcase_duration = 0.15
|
||||
assert(player.bag.add_item(&"crab_net"))
|
||||
assert(player.hotbar.assign_item(0, &"crab_net"))
|
||||
|
|
@ -82,6 +88,18 @@ func _run() -> void:
|
|||
await process_frame
|
||||
assert(fishing_spot.get("_pending_catch") == null)
|
||||
|
||||
# A radial menu can open after the showcase panel disappears but before the
|
||||
# pocket animation restores gameplay. Its camera lock must not overwrite the
|
||||
# showcase's underlying camera state when the two finish in either order.
|
||||
Input.action_press("open_emotes")
|
||||
var emote_press := InputEventAction.new()
|
||||
emote_press.action = &"open_emotes"
|
||||
emote_press.pressed = true
|
||||
game_ui.call("_input", emote_press)
|
||||
var emote_menu := game_ui.get_node("%EmoteRadialMenu") as EmoteRadialMenu
|
||||
assert(emote_menu.is_open())
|
||||
assert(not bool(player.call("_is_camera_input_enabled")))
|
||||
|
||||
var pocket_deadline: int = Time.get_ticks_msec() + 6000
|
||||
while (
|
||||
Time.get_ticks_msec() < pocket_deadline
|
||||
|
|
@ -91,6 +109,23 @@ func _run() -> void:
|
|||
assert(fishing_spot.state == FishingSpot.FishingState.READY)
|
||||
assert(player.is_movement_enabled())
|
||||
assert(bool(player.call("_is_movement_input_enabled")))
|
||||
assert(not bool(player.call("_is_camera_input_enabled")))
|
||||
Input.action_release("open_emotes")
|
||||
var emote_release := InputEventAction.new()
|
||||
emote_release.action = &"open_emotes"
|
||||
emote_release.pressed = false
|
||||
game_ui.call("_input", emote_release)
|
||||
assert(not emote_menu.is_open())
|
||||
assert(bool(player.call("_is_camera_input_enabled")))
|
||||
|
||||
# A focused chat field or popup can consume the wheel's release event. The
|
||||
# polled action state must still close the radial and release its camera lock.
|
||||
Input.action_press("open_emotes")
|
||||
game_ui.call("_input", emote_press)
|
||||
assert(emote_menu.is_open())
|
||||
Input.action_release("open_emotes")
|
||||
await process_frame
|
||||
assert(not emote_menu.is_open())
|
||||
assert(bool(player.call("_is_camera_input_enabled")))
|
||||
assert(player.inventory.contains_catch_id(crab_catch.catch_id))
|
||||
assert(bool(player.get("_active_item_is_net")))
|
||||
|
|
|
|||
105
ui/game_ui.gd
105
ui/game_ui.gd
|
|
@ -81,6 +81,8 @@ signal shop_backdrop_visibility_changed(is_visible: bool)
|
|||
signal virtual_pointer_mode_changed(is_active: bool)
|
||||
|
||||
const VIRTUAL_MOUSE_INPUT_OWNER: StringName = &"controller_virtual_mouse"
|
||||
const EMOTE_RADIAL_CAMERA_OWNER: StringName = &"emote_radial_menu"
|
||||
const QUICK_RADIAL_CAMERA_OWNER: StringName = &"quick_radial_menu"
|
||||
const VIRTUAL_MOUSE_TRIGGER_THRESHOLD: float = 0.55
|
||||
const VIRTUAL_MOUSE_TRIGGER_RELEASE_THRESHOLD: float = 0.35
|
||||
const VIRTUAL_MOUSE_STICK_DEADZONE: float = 0.18
|
||||
|
|
@ -194,8 +196,6 @@ var _experience_award_queue: Array[Dictionary] = []
|
|||
var _experience_animation_active: bool = false
|
||||
var _experience_animation_generation: int = 0
|
||||
var _experience_panel_rest_y: float = 18.0
|
||||
var _emote_prior_camera_input_enabled: bool = true
|
||||
var _quick_prior_camera_input_enabled: bool = true
|
||||
var _virtual_mouse_active: bool = false
|
||||
var _virtual_mouse_prior_camera_input_enabled: bool = true
|
||||
var _virtual_mouse_prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
|
||||
|
|
@ -581,15 +581,10 @@ func _input(event: InputEvent) -> void:
|
|||
):
|
||||
var emote_is_open: bool = _emote_radial_menu.is_open()
|
||||
if emote_is_open != emote_was_open and _player != null:
|
||||
if emote_is_open:
|
||||
_emote_prior_camera_input_enabled = (
|
||||
_player.is_camera_input_enabled()
|
||||
)
|
||||
_player.set_camera_input_enabled(false)
|
||||
else:
|
||||
_player.set_camera_input_enabled(
|
||||
_emote_prior_camera_input_enabled
|
||||
)
|
||||
_player.set_camera_input_suppressed(
|
||||
EMOTE_RADIAL_CAMERA_OWNER,
|
||||
emote_is_open,
|
||||
)
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
var quick_was_open: bool = _quick_radial_menu.is_open()
|
||||
|
|
@ -599,18 +594,63 @@ func _input(event: InputEvent) -> void:
|
|||
):
|
||||
var quick_is_open: bool = _quick_radial_menu.is_open()
|
||||
if quick_is_open != quick_was_open and _player != null:
|
||||
if quick_is_open:
|
||||
_quick_prior_camera_input_enabled = (
|
||||
_player.is_camera_input_enabled()
|
||||
)
|
||||
_player.set_camera_input_enabled(false)
|
||||
else:
|
||||
_player.set_camera_input_enabled(
|
||||
_quick_prior_camera_input_enabled
|
||||
)
|
||||
_player.set_camera_input_suppressed(
|
||||
QUICK_RADIAL_CAMERA_OWNER,
|
||||
quick_is_open,
|
||||
)
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _reconcile_radial_menu_input() -> void:
|
||||
var blocked: bool = (
|
||||
not _gameplay_ui_enabled
|
||||
or _system_menu_open
|
||||
or _player_menu_open
|
||||
or _shop_open
|
||||
or _storage_open
|
||||
or _chat_input_open
|
||||
or _showcase_active
|
||||
or _virtual_mouse_active
|
||||
)
|
||||
if (
|
||||
_emote_radial_menu != null
|
||||
and _emote_radial_menu.is_open()
|
||||
and (blocked or not Input.is_action_pressed("open_emotes"))
|
||||
):
|
||||
_close_emote_radial_menu()
|
||||
if (
|
||||
_quick_radial_menu != null
|
||||
and _quick_radial_menu.is_open()
|
||||
and (blocked or not Input.is_action_pressed("open_quick_actions"))
|
||||
):
|
||||
_close_quick_radial_menu()
|
||||
|
||||
|
||||
func _close_radial_menus() -> void:
|
||||
_close_emote_radial_menu()
|
||||
_close_quick_radial_menu()
|
||||
|
||||
|
||||
func _close_emote_radial_menu() -> void:
|
||||
if _emote_radial_menu != null and _emote_radial_menu.is_open():
|
||||
_emote_radial_menu.close_menu()
|
||||
if _player != null and is_instance_valid(_player):
|
||||
_player.set_camera_input_suppressed(
|
||||
EMOTE_RADIAL_CAMERA_OWNER,
|
||||
false,
|
||||
)
|
||||
|
||||
|
||||
func _close_quick_radial_menu() -> void:
|
||||
if _quick_radial_menu != null and _quick_radial_menu.is_open():
|
||||
_quick_radial_menu.close_menu()
|
||||
if _player != null and is_instance_valid(_player):
|
||||
_player.set_camera_input_suppressed(
|
||||
QUICK_RADIAL_CAMERA_OWNER,
|
||||
false,
|
||||
)
|
||||
|
||||
|
||||
func _can_use_character_call() -> bool:
|
||||
return (
|
||||
_gameplay_ui_enabled
|
||||
|
|
@ -1356,6 +1396,7 @@ func is_input_mapping_capturing() -> bool:
|
|||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_reconcile_radial_menu_input()
|
||||
_poll_virtual_mouse_controller_state()
|
||||
_update_virtual_mouse(delta)
|
||||
_update_controller_menu_scroll(delta)
|
||||
|
|
@ -1578,18 +1619,7 @@ func set_gameplay_ui_enabled(enabled: bool) -> void:
|
|||
_refresh_chat_availability()
|
||||
if not enabled:
|
||||
_end_virtual_mouse()
|
||||
if _emote_radial_menu != null and _emote_radial_menu.is_open():
|
||||
_emote_radial_menu.close_menu()
|
||||
if _player != null:
|
||||
_player.set_camera_input_enabled(
|
||||
_emote_prior_camera_input_enabled
|
||||
)
|
||||
if _quick_radial_menu != null and _quick_radial_menu.is_open():
|
||||
_quick_radial_menu.close_menu()
|
||||
if _player != null:
|
||||
_player.set_camera_input_enabled(
|
||||
_quick_prior_camera_input_enabled
|
||||
)
|
||||
_close_radial_menus()
|
||||
if _surface_drawing != null:
|
||||
_surface_drawing.deactivate()
|
||||
close_player_menu_for_session_end()
|
||||
|
|
@ -1658,6 +1688,8 @@ func _refresh_gameplay_hud_visibility() -> void:
|
|||
|
||||
func set_system_menu_open(is_open: bool) -> void:
|
||||
_system_menu_open = is_open
|
||||
if is_open:
|
||||
_close_radial_menus()
|
||||
_refresh_surface_drawing_activation()
|
||||
_refresh_gameplay_hud_visibility()
|
||||
_refresh_chat_availability()
|
||||
|
|
@ -2071,6 +2103,8 @@ func _on_showcase_changed(
|
|||
showcase_visible: bool,
|
||||
) -> void:
|
||||
_showcase_active = showcase_visible
|
||||
if showcase_visible:
|
||||
_close_radial_menus()
|
||||
_refresh_surface_drawing_activation()
|
||||
if not showcase_visible:
|
||||
_set_fishing_panel_showcase_position(false)
|
||||
|
|
@ -2314,6 +2348,7 @@ func _on_player_menu_visibility_changed(is_open: bool) -> void:
|
|||
_chat_ui.set_world_speech_visible(not is_open)
|
||||
if is_open:
|
||||
_end_virtual_mouse()
|
||||
_close_radial_menus()
|
||||
_refresh_surface_drawing_activation()
|
||||
_refresh_gameplay_hud_visibility()
|
||||
if is_open:
|
||||
|
|
@ -2366,6 +2401,8 @@ func _on_shop_exit_started() -> void:
|
|||
|
||||
func _on_shop_visibility_changed(is_open: bool) -> void:
|
||||
_shop_open = is_open
|
||||
if is_open:
|
||||
_close_radial_menus()
|
||||
_refresh_surface_drawing_activation()
|
||||
if is_open:
|
||||
shop_backdrop_visibility_changed.emit(true)
|
||||
|
|
@ -2389,6 +2426,8 @@ func _on_shop_visibility_changed(is_open: bool) -> void:
|
|||
|
||||
func _on_storage_visibility_changed(is_open: bool) -> void:
|
||||
_storage_open = is_open
|
||||
if is_open:
|
||||
_close_radial_menus()
|
||||
_refresh_surface_drawing_activation()
|
||||
_refresh_gameplay_hud_visibility()
|
||||
_refresh_chat_availability()
|
||||
|
|
@ -2544,6 +2583,8 @@ func _emit_interactive_pointer_ui_changed() -> void:
|
|||
|
||||
func _on_chat_text_entry_ownership_changed(active: bool) -> void:
|
||||
_chat_input_open = active
|
||||
if active:
|
||||
_close_radial_menus()
|
||||
_refresh_surface_drawing_activation()
|
||||
_refresh_chat_availability()
|
||||
_emit_interactive_pointer_ui_changed()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue