farming-simulator/scripts/player/fpv_controller.gd
Dmitrii Bykov f8040fb399
Some checks failed
Godot CI / Import + smoke test (push) Failing after 13s
полная мета-структура игры: pause + settings + save/load
PAUSE-МЕНЮ (autoload PauseMenu CanvasLayer):
- ESC во время геймплея открывает оверлей с тёмным фоном
  и центральной панелью в Шиндовс-стиле.
- 4 кнопки: Продолжить / Настройки / В главное меню /
  Сохранить и выйти.
- process_mode = ALWAYS чтобы работать при tree.paused = true.
- При закрытии меню в 3D-сценах автоматически возвращает
  Input.mouse_mode = CAPTURED.
- В главном меню pause не активируется (_can_pause false).
- set_input_as_handled блокирует FPV-контроллер от перехвата
  ESC одновременно.

НАСТРОЙКИ (под-панель в Pause-меню):
- Слайдер «Чувствительность мыши» 0.001-0.010 (по умолчанию 0.0025).
- Слайдер «Громкость» 0-100% — связан с AudioServer Bus 0 Master
  через linear_to_db (audio ассетов пока нет, но шкала рабочая).
- Кнопки Назад / Применить. Применить пишет в user://settings.json.
- settings.json грузится автоматически при старте GameState._ready.

SAVE/LOAD:
- GameState.save_game(scene_path) → user://save01.json в JSON
  с player_name, прогрессом актов 1-2, ачивками, текущей сценой.
- GameState.load_game() читает и восстанавливает state, заполняет
  last_scene_path для перехода.
- has_save() / delete_save() помощники.
- Кнопка «Продолжить» в главном меню активна только если есть
  save и грузит игрока в сохранённую сцену.
- Кнопка «Новая игра» удаляет старый save и сбрасывает state.

AUTO-SAVE:
- При закрытии окна через X (NOTIFICATION_WM_CLOSE_REQUEST) —
  save_game перед quit.
- Кнопка Q в FPV-контроллере — save+quit.
- Кнопка «Сохранить и выйти» в pause-меню — save+quit.
- Кнопка «В главное меню» — save перед переходом.

FPV-КОНТРОЛЛЕР:
- mouse_sensitivity больше не локальное @export, читается из
  GameState.mouse_sensitivity каждый mouse_motion event
  (применяется живьём после Apply).
- _input → _unhandled_input чтобы PauseMenu мог перехватить ESC
  через set_input_as_handled.
- Убран ESC-toggle захвата мыши (теперь это делает PauseMenu).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:28:58 +03:00

97 lines
2.6 KiB
GDScript

class_name FPVController extends CharacterBody3D
@export var move_speed: float = 3.0
@export var mouse_sensitivity: float = 0.0025
@export var gravity: float = 9.8
@onready var camera: Camera3D = $Camera3D
@onready var interact_ray: RayCast3D = $Camera3D/InteractRay
signal interactable_focused(obj: Node)
signal interactable_unfocused
signal interact_pressed(obj: Node)
var _captured: bool = false
var _locked: bool = false
var _focused_obj: Node = null
func _ready() -> void:
_capture_mouse()
func _capture_mouse() -> void:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
_captured = true
func _release_mouse() -> void:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
_captured = false
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseMotion and _captured and not _locked:
var sens: float = GameState.mouse_sensitivity
rotate_y(-event.relative.x * sens)
camera.rotate_x(-event.relative.y * sens)
camera.rotation.x = clamp(camera.rotation.x, -PI * 0.45, PI * 0.45)
elif event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
if not _captured:
_capture_mouse()
return
if _locked:
return
if _focused_obj != null:
interact_pressed.emit(_focused_obj)
if _focused_obj.has_method("interact"):
_focused_obj.interact(self)
elif event is InputEventKey and event.pressed:
if event.keycode == KEY_Q:
GameState.save_game(get_tree().current_scene.scene_file_path if get_tree().current_scene else "")
get_tree().quit()
func _physics_process(delta: float) -> void:
if _locked:
velocity = Vector3.ZERO
move_and_slide()
_update_focus()
return
var input_dir: Vector2 = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
var direction: Vector3 = (transform.basis * Vector3(input_dir.x, 0.0, input_dir.y)).normalized()
velocity.x = direction.x * move_speed
velocity.z = direction.z * move_speed
if not is_on_floor():
velocity.y -= gravity * delta
else:
velocity.y = 0.0
move_and_slide()
_update_focus()
func _update_focus() -> void:
interact_ray.force_raycast_update()
var new_focus: Node = null
if interact_ray.is_colliding():
var col: Object = interact_ray.get_collider()
if col != null and col is Node and col.has_method("get_prompt"):
new_focus = col
if new_focus != _focused_obj:
if new_focus != null:
interactable_focused.emit(new_focus)
else:
interactable_unfocused.emit()
_focused_obj = new_focus
func lock_input() -> void:
_locked = true
func unlock_input() -> void:
_locked = false
func get_focused() -> Node:
return _focused_obj