using UnityEngine;
using System;
using UnityEngine.InputSystem;
///
/// Централизованный ввод на основе New Input System.
/// Singleton, DontDestroyOnLoad.
/// Биндинги сохраняются в PlayerPrefs автоматически при изменении.
///
public class PlayerInput : MonoBehaviour
{
public static PlayerInput Instance { get; private set; }
// ── Ссылка на InputActionAsset ────────────────────────────────────────────
[Tooltip("Назначьте Assets/Input/PlayerControls.inputactions")]
public InputActionAsset actions;
// ── Непрерывные значения ──────────────────────────────────────────────────
public Vector2 MoveInput { get; private set; }
public Vector2 LookInput { get; private set; }
public bool Crouch { get; private set; }
public bool Run { get; private set; }
// ── Дискретные события ────────────────────────────────────────────────────
public event Action OnToggleInventory;
public event Action OnDropPressed;
public event Action OnPickupPressed;
public event Action OnJumpPressed;
public event Action OnHotbarSelect;
// ── Включение/выключение gameplay-ввода (инвентарь открыт) ───────────────
public bool Enabled
{
get => _gameplayMap != null && _gameplayMap.enabled;
set
{
if (_gameplayMap == null) return;
if (value) _gameplayMap.Enable();
else _gameplayMap.Disable();
}
}
// ── Приватные поля ────────────────────────────────────────────────────────
private InputActionMap _gameplayMap;
private InputActionMap _uiMap;
private const string BINDINGS_PREF_KEY = "PlayerBindings_v1";
// ── Lifecycle ─────────────────────────────────────────────────────────────
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
return;
}
if (actions == null)
{
actions = Resources.Load("PlayerControls");
if (actions == null)
{
Debug.LogError("[PlayerInput] PlayerControls.inputactions не назначен и не найден в Resources!");
return;
}
}
// Создаём рабочую копию чтобы не менять оригинальный asset
actions = Instantiate(actions);
// Загрузить сохранённые биндинги
var saved = PlayerPrefs.GetString(BINDINGS_PREF_KEY, "");
if (!string.IsNullOrEmpty(saved))
actions.LoadBindingOverridesFromJson(saved);
_gameplayMap = actions.FindActionMap("Gameplay", throwIfNotFound: true);
_uiMap = actions.FindActionMap("UI", throwIfNotFound: true);
// Подписка: дискретные кнопки
_gameplayMap["Jump"].performed += _ => OnJumpPressed?.Invoke();
_gameplayMap["Pickup"].performed += _ => OnPickupPressed?.Invoke();
_gameplayMap["Drop"].performed += _ => OnDropPressed?.Invoke();
// UI map всегда активна — нет double-fire бага
_uiMap["ToggleInventory"].performed += _ => OnToggleInventory?.Invoke();
_uiMap["CloseInventory"].performed += _ => OnToggleInventory?.Invoke();
for (int i = 0; i < 9; i++)
{
int idx = i;
_gameplayMap[$"Hotbar{idx}"].performed += _ => OnHotbarSelect?.Invoke(idx);
}
_gameplayMap.Enable();
_uiMap.Enable();
}
void OnDestroy()
{
_gameplayMap?.Disable();
_uiMap?.Disable();
}
void Update()
{
if (_gameplayMap == null || !_gameplayMap.enabled) return;
MoveInput = _gameplayMap["Move"].ReadValue();
LookInput = _gameplayMap["Look"].ReadValue();
Crouch = _gameplayMap["Crouch"].IsPressed();
Run = _gameplayMap["Run"].IsPressed();
}
// ── Сохранение/загрузка биндингов ────────────────────────────────────────
[ContextMenu("Save Bindings")]
public void SaveBindings()
{
if (actions == null) return;
PlayerPrefs.SetString(BINDINGS_PREF_KEY, actions.SaveBindingOverridesAsJson());
PlayerPrefs.Save();
Debug.Log("[PlayerInput] Биндинги сохранены");
}
[ContextMenu("Reset Bindings to Defaults")]
public void ResetBindings()
{
if (actions == null) return;
foreach (var action in actions)
action.RemoveAllBindingOverrides();
PlayerPrefs.DeleteKey(BINDINGS_PREF_KEY);
Debug.Log("[PlayerInput] Биндинги сброшены к умолчаниям");
}
///
/// Интерактивный ремаппинг. Передайте имя action, имя map и callback для UI.
/// Пример: StartRebind("Jump", "Gameplay", () => UpdateUI());
///
public void StartRebind(string actionName, string mapName, Action onComplete)
{
var map = actions?.FindActionMap(mapName);
var action = map?.FindAction(actionName);
if (action == null)
{
Debug.LogWarning($"[PlayerInput] Действие '{actionName}' в map '{mapName}' не найдено");
return;
}
action.Disable();
action.PerformInteractiveRebinding()
.WithControlsExcluding("/position")
.WithControlsExcluding("/delta")
.OnComplete(op =>
{
op.Dispose();
action.Enable();
SaveBindings();
onComplete?.Invoke();
})
.Start();
}
///
/// Доступ к InputActionAsset для UI-ремаппинга и отображения текущих биндингов.
///
public InputActionAsset GetActions() => actions;
}